
/* GCSx
** FRAME.CPP
**
** Frame windows
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

// @TODO: several [pos] loops that refer to clientArea could be iterator-based now

#include "all.h"

#define DEBUG_INFO_CLIENTS \
    debugWrite("Clients %s: (0-%d)", debugDump(), clientArea.size() - 1); \
    for (int debug_pos = 0; debug_pos < (int)clientArea.size(); ++debug_pos) { \
        if (clientArea[debug_pos].window) { \
            debugWrite("  %d: %d,%d (%d x %d, show %d x %d) %s", debug_pos, \
                       clientArea[debug_pos].x, clientArea[debug_pos].y, \
                       clientArea[debug_pos].width, clientArea[debug_pos].height, \
                       clientArea[debug_pos].widthShown, clientArea[debug_pos].heightShown, \
                       clientArea[debug_pos].window->debugDump()); \
        } \
    }

int FrameWindow::cascadeStep = -1;
int FrameWindow::minimumPanelSize = 0;

const char* FrameWindow::wtButtonAll = "hgf";
const char* FrameWindow::wtButtonNoResize = "hf";
const string FrameWindow::wtButtonClose("f");
const string FrameWindow::wtButtonUp("m");
const string FrameWindow::wtButtonDown("n");
const string FrameWindow::wtButtonLeft("l");
const string FrameWindow::wtButtonRight("k");

FrameWindow::FrameWindow(const string& title, ResizableType isResizable, FrameType type, Window* client, TitlebarType hasTitlebarType, int hasOuterBorder, FrameWindow* isFramePanel) : Window(), titlebar(title), clientArea() { start_func
    assert(type != FRAMETYPE_NOCHANGE);
    assert(hasTitlebarType != TITLEBAR_NOCHANGE);
    assert(isResizable != RESIZING_NOCHANGE);
    assert(hasOuterBorder != -1);

    minimumPanelSize = fontHeight(FONT_TOOLTITLEBAR) + FRAME_TOOLTITLETOPPAD + FRAME_TOOLTITLEBOTTOMPAD;
    
    // This is the first place we might error- so far, nothing needs undoing until client is pushed
    ClientArea ca = { client, -1, -1, -1, -1, -1, -1, CLIENT_PRIMARY };
    clientArea.push_back(ca);
    
    state = FRAME_STATE_NORMAL;
    dragMode = FRAME_DRAG_NONE;
    deleteMe = 1;
    haveFocus = subFocus = 0;
    scrollX = scrollY = 0;
    scrollRepeatTicks = scrollRepeatDelay = 0;
    hScrollLine = vScrollLine = fontHeight();
    whyDirty = FRAME_DIRTY_ALL;
    floatTarget = 0;
    autoCenterScroll = 0;

    reserve[0] = reserve[1] = reserve[2] = reserve[3] = 0;
    
    // (we set outerBorder only because setFeatures looks at it)
    outerBorder = 0;
    framePanel = NULL;
    
    setFeatures(isResizable, type, hasTitlebarType, hasOuterBorder, isFramePanel, 1);
    
    // Initial size
    if (client) {
        client->setParent(this);
        width = client->getWidth();
        if (width < titlebarButtonTotalWidth) width = titlebarButtonTotalWidth;
        height = client->getHeight();
        if (height < 0) height = 0;
        width += outerBorder * 2 + FRAME_INNERBORDER * 2;
        height += outerBorder * 2 + FRAME_INNERBORDER * 2 + titlebarHeight;
    }
    else {
        width = outerBorder * 2 + 2 + titlebarButtonTotalWidth;
        height = titlebarHeight + 2 * outerBorder;
    }
}

void FrameWindow::setFeatures(ResizableType isResizable, FrameType type, TitlebarType hasTitlebarType, int hasOuterBorder, FrameWindow* isFramePanel, int skipResizing) { start_func
    int oldOuterBorder = outerBorder;

    if (hasTitlebarType != TITLEBAR_NOCHANGE) titlebarType = hasTitlebarType;
    if (isFramePanel != NULL) framePanel = isFramePanel;
    if (hasOuterBorder != -1) outerBorder = hasOuterBorder ? FRAME_OUTERBORDER : 0;
    if (isResizable != RESIZING_NOCHANGE) resizable = isResizable;
    if (type != FRAMETYPE_NOCHANGE) frameType = type;

    // Recalculate our constants- small chance of VideoException in this section
    titlebarX = outerBorder + FRAME_INNERBORDER - FRAME_BEVELTHICKNESS;
    titlebarY = outerBorder;
    titlebarHeight = (titlebarType == TITLEBAR_TOOL ? FRAME_TOOLTITLETOPPAD + FRAME_TOOLTITLEBOTTOMPAD : FRAME_TITLETOPPAD + FRAME_TITLEBOTTOMPAD) + fontHeight(titlebarType == TITLEBAR_TOOL ? FONT_TOOLTITLEBAR : FONT_TITLEBAR);
    titlebarButtonY = titlebarY + 1;
    titlebarButtonSize = titlebarHeight - 2;
    titlebarButtonXPad = (titlebarButtonSize - fontWidth(wtButtonClose, titlebarType == TITLEBAR_TOOL ? FONT_TOOLTITLEBARWIDGET : FONT_TITLEBARWIDGET)) / 2;
    scrollbarButtonSize = fontHeight(FONT_WIDGET) + 2;
    scrollbarButtonXPad = (scrollbarButtonSize - fontWidth(wtButtonClose, FONT_WIDGET)) / 2;

    if (titlebarType == TITLEBAR_OFF) {
        titlebarHeight = 0;
        titlebarButtons[0] = 0;
    }
    else if ((resizable) && (!framePanel)) {
        strcpy(titlebarButtons, wtButtonAll);
    }
    else {
        strcpy(titlebarButtons, wtButtonNoResize);
    }
    if (state == FRAME_STATE_MINIMIZED) {
        char* button = strchr(titlebarButtons, 'h');
        if (button) *button = 'j';
    }
    if (state == FRAME_STATE_MAXIMIZED) {
        char* button = strchr(titlebarButtons, 'g');
        if (button) *button = 'j';
    }

    titlebarButtonTotalWidth = titlebarButtonSize * strlen(titlebarButtons);

    // Resize to fit new outer border and minimum titlebar width
    if (!skipResizing) {
        int newWidth = width + outerBorder * 2 - oldOuterBorder * 2;
        int newHeight = height + outerBorder * 2 - oldOuterBorder * 2;
        if (newWidth < outerBorder * 2 + 2 + titlebarButtonTotalWidth) newWidth = outerBorder * 2 + 2 + titlebarButtonTotalWidth;
        int newX = x;
        int newY = y;

        confineSize(newX, newY, newWidth, newHeight);
        move(newX, newY);
        resize(newWidth, newHeight);
    }
}
FrameWindow* FrameWindow::getFramePanel() { start_func
    return framePanel;
}
    
void FrameWindow::requestViewSize(int requestWidth, int requestHeight) { start_func
    if (state != FRAME_STATE_MAXIMIZED) {
        requestWidth += width - clientArea[0].width;
        requestHeight += height - clientArea[0].height;
        
        if (width > requestWidth) requestWidth = width;
        if (height > requestHeight) requestHeight = height;
        
        int newX = x;
        int newY = y;
        confineSize(newX, newY, requestWidth, requestHeight);
        move(newX, newY);
        resize(requestWidth, requestHeight);
    }
}

void FrameWindow::dropClients() { start_func
    vector<ClientArea>::iterator end = clientArea.end();
    for (vector<ClientArea>::iterator pos = clientArea.begin(); pos != end; ++pos) {
        if ((*pos).window) {
            (*pos).window->setParent(NULL);
            (*pos).window = NULL;
        }
    }
}

// Determines size of reserves based on existing window size, then relocates
// all tool windows and central client window
void FrameWindow::reorganizeReserves() { start_func
    int max = clientArea.size();
    int countPanel[4];
    
    // Determine max sizes of all items
    for (int spot = CLIENT_RIGHT; spot <= CLIENT_BOTTOM; ++spot) {
        reserve[spot] = 0;
        countPanel[spot] = 0;
        for (int pos = 1; pos < max; ++pos) {
            if (clientArea[pos].window) {
                // Pull size from window itself?
                if (clientArea[pos].width < 0) clientArea[pos].width = clientArea[pos].window->getWidth();
                if (clientArea[pos].height < 0) clientArea[pos].height = clientArea[pos].window->getHeight();
                // (while we're here, unhide everything for now)
                clientArea[pos].position = clientArea[pos].position & ~CLIENT_HIDDEN;
                if (clientArea[pos].position == spot) {
                    ++countPanel[spot];
                    if (spot & CLIENT_TOPBOTTOM) {
                        if (clientArea[pos].height > reserve[spot]) {
                            reserve[spot] = clientArea[pos].height;
                        }
                    }
                    else {
                        if (clientArea[pos].width > reserve[spot]) {
                            reserve[spot] = clientArea[pos].width;
                        }
                    }
                }
            }
        }
        
        // Add in gutter if needed
        if (reserve[spot]) {
            reserve[spot] += FRAME_CLIENTPAD;
        }
    }
    
    // Start with entire area dedicated to client area INCLUDING inner border
    int mainWidth = width - (outerBorder * 2);
    int mainHeight = height - (outerBorder * 2 + titlebarHeight);
    int mainX = outerBorder;
    int mainY = titlebarY + titlebarHeight;
    
    // Add in each area that exists, as long as client area has space to "give"
    // If not, "hide" area
    // Add in top?
    if ((reserve[CLIENT_TOP]) && (mainWidth > 0) && (mainHeight > 0)) {
        if (mainHeight < reserve[CLIENT_TOP]) {
            reserve[CLIENT_TOP] = mainHeight;
        }
        mainY += reserve[CLIENT_TOP];
        mainHeight -= reserve[CLIENT_TOP];
        reserve[CLIENT_TOP] -= FRAME_CLIENTPAD;
    }
    else {
        reserve[CLIENT_TOP] = 0;
    }
    
    // Bottom?
    if ((reserve[CLIENT_BOTTOM]) && (mainWidth > 0) && (mainHeight > 0)) {
        if (mainHeight < reserve[CLIENT_BOTTOM]) {
            reserve[CLIENT_BOTTOM] = mainHeight;
        }
        mainHeight -= reserve[CLIENT_BOTTOM];
        reserve[CLIENT_BOTTOM] -= FRAME_CLIENTPAD;
    }
    else {
        reserve[CLIENT_BOTTOM] = 0;
    }

    // Left?
    if ((reserve[CLIENT_LEFT]) && (mainWidth > 0) && (mainHeight > 0)) {
        if (mainWidth < reserve[CLIENT_LEFT]) {
            reserve[CLIENT_LEFT] = mainWidth;
        }
        mainX += reserve[CLIENT_LEFT];
        mainWidth -= reserve[CLIENT_LEFT];
        reserve[CLIENT_LEFT] -= FRAME_CLIENTPAD;
    }
    else {
        reserve[CLIENT_LEFT] = 0;
    }

    // Right?
    if ((reserve[CLIENT_RIGHT]) && (mainWidth > 0) && (mainHeight > 0)) {
        if (mainWidth < reserve[CLIENT_RIGHT]) {
            reserve[CLIENT_RIGHT] = mainWidth;
        }
        mainWidth -= reserve[CLIENT_RIGHT];
        reserve[CLIENT_RIGHT] -= FRAME_CLIENTPAD;
    }
    else {
        reserve[CLIENT_RIGHT] = 0;
    }
    
    // Loop through all areas again, placing in proper positions or hiding as needed
    for (int spot = CLIENT_RIGHT; spot <= CLIENT_BOTTOM; ++spot) {
        int nextCoord = 0;
        int lastDid = 0;
        // Minimum size for all panels combined (reduces as we proceed)
        int minSizeForAll = (minimumPanelSize + FRAME_CLIENTPAD) * countPanel[spot];
        for (int pos = 1; pos < max; ++pos) {
            if ((clientArea[pos].position == spot) && (clientArea[pos].window)) {
                // Hiding entire side?
                if (reserve[spot] <= 0) {
                    clientArea[pos].position |= CLIENT_HIDDEN;
                }
                else {
                    // Account for our share of the minimum size
                    minSizeForAll -= minimumPanelSize + FRAME_CLIENTPAD;
                    
                    // Assume we get entire desired area for now
                    clientArea[pos].widthShown = clientArea[pos].width;
                    clientArea[pos].heightShown = clientArea[pos].height;
                        
                    // Place in next position
                    if (spot & CLIENT_TOPBOTTOM) {
                        // Horizontal position
                        clientArea[pos].x = nextCoord + outerBorder;

                        // Clip width? (accounting for min size of future panels)
                        if (clientArea[pos].x + clientArea[pos].widthShown > width - outerBorder - minSizeForAll) {
                            clientArea[pos].widthShown = width - outerBorder - clientArea[pos].x - minSizeForAll;
                        }
                        // Minimum size
                        if (clientArea[pos].widthShown < minimumPanelSize) {
                            clientArea[pos].widthShown = minimumPanelSize;
                        }
                        // Clip width? (if minimum size is too much, not accounting for future panels)
                        if (clientArea[pos].x + clientArea[pos].widthShown > width - outerBorder) {
                            clientArea[pos].widthShown = width - outerBorder - clientArea[pos].x;
                        }
                        // Hidden?
                        if (clientArea[pos].widthShown <= 0) {
                            clientArea[pos].position |= CLIENT_HIDDEN;
                        }

                        // Next position
                        nextCoord += clientArea[pos].widthShown + FRAME_CLIENTPAD;

                        // Height is elementary
                        clientArea[pos].heightShown = reserve[spot];

                        // Vertical position is elementary
                        if (spot == CLIENT_TOP) {
                            clientArea[pos].y = titlebarY + titlebarHeight;
                        }
                        else {
                            clientArea[pos].y = mainY + mainHeight + FRAME_CLIENTPAD;
                        }
                    }
                    else {
                        // Vertical position
                        clientArea[pos].y = nextCoord + titlebarY + titlebarHeight + reserve[CLIENT_TOP];

                        // Clip height? (accounting for min size of future panels)
                        if (clientArea[pos].y + clientArea[pos].heightShown > mainY + mainHeight - minSizeForAll) {
                            clientArea[pos].heightShown = mainY + mainHeight - clientArea[pos].y - minSizeForAll;
                        }
                        // Minimum size
                        if (clientArea[pos].heightShown < minimumPanelSize) {
                            clientArea[pos].heightShown = minimumPanelSize;
                        }
                        // Clip height? (if minimum size is too much, not accounting for future panels)
                        if (clientArea[pos].y + clientArea[pos].heightShown > mainY + mainHeight) {
                            clientArea[pos].heightShown = mainY + mainHeight - clientArea[pos].y;
                        }
                        // Hidden?
                        if (clientArea[pos].heightShown <= 0) {
                            clientArea[pos].position |= CLIENT_HIDDEN;
                        }

                        // Next position
                        nextCoord += clientArea[pos].heightShown + FRAME_CLIENTPAD;

                        // Width is elementary
                        clientArea[pos].widthShown = reserve[spot];

                        // Horizontal position is elementary
                        if (spot == CLIENT_LEFT) {
                            clientArea[pos].x = outerBorder;
                        }
                        else {
                            clientArea[pos].x = mainX + mainWidth + FRAME_CLIENTPAD;
                        }
                    }
                    
                    lastDid = pos;
                }
                
                if (clientArea[pos].position & CLIENT_HIDDEN) {
                    clientArea[pos].widthShown = 0;
                    clientArea[pos].heightShown = 0;
                }
            }
        }
        
        // We now resize the last one, if it wasn't hidden- if there's more space
        if ((lastDid) && (!(clientArea[lastDid].position & CLIENT_HIDDEN))) {
            if (spot & CLIENT_TOPBOTTOM) {
                // Extend width?
                if (clientArea[lastDid].x + clientArea[lastDid].widthShown < width - outerBorder) {
                    clientArea[lastDid].widthShown = width - outerBorder - clientArea[lastDid].x;
                }
            }
            else {
                // Extend height?
                if (clientArea[lastDid].y + clientArea[lastDid].heightShown < mainY + mainHeight) {
                    clientArea[lastDid].heightShown = mainY + mainHeight - clientArea[lastDid].y;
                }
            }
        }
    }
    
    // Client area now loses space for inner borders
    mainWidth -= FRAME_INNERBORDER * 2;
    mainHeight -= FRAME_INNERBORDER * 2;
    
    if (mainWidth < 0) mainWidth = 0;
    if (mainHeight < 0) mainHeight = 0;

    // Finally, we apply the calculations to the main client area
    clientArea[0].x = mainX + FRAME_INNERBORDER;
    clientArea[0].y = mainY + FRAME_INNERBORDER;
    clientArea[0].width = mainWidth;
    clientArea[0].height = mainHeight;
    clientArea[0].widthShown = mainWidth;
    clientArea[0].heightShown = mainHeight;
}

int FrameWindow::findToolPanel(Window* toolPanel) { start_func
    assert(toolPanel);

    for (int pos = 1; pos < (int)clientArea.size(); ++pos) {
        if (clientArea[pos].window == toolPanel) return pos;
    }
    
    return 0;
}

void FrameWindow::addToolPanel(FrameWindow* toolPanel, int position, vector<ClientArea>::iterator* insertBefore) { start_func
    assert(toolPanel);

    position &= ~CLIENT_HIDDEN;
    
    int newHeight = height;
    int newWidth = width;
    int newX = x;
    int newY = y;

    int desiredSize = -1;
    int wasMinimized = toolPanel->isMinimized();
    if (wasMinimized) toolPanel->restore();

    // Do this now so panel size is as it will be (border and all)
    // Add as managed (temporarily ensure not notifying anyone, to eliminate redundant notifications)
    toolPanel->setParentNotify(NULL);
    toolPanel->setParent(NULL);
    if (position == CLIENT_FLOAT) { // (but not closed)
        toolPanel->show(SHOW_CURRENT, SHOW_CURRENT, SHOW_CURRENT, SHOW_CURRENT);
        toolPanel->setFeatures(RESIZING_NOCHANGE, FRAMETYPE_NOCHANGE, TITLEBAR_NOCHANGE, 1, this);
    }
    else {
        toolPanel->hide();
        toolPanel->setFeatures(RESIZING_NOCHANGE, FRAMETYPE_NOCHANGE, TITLEBAR_NOCHANGE, 0, this);
        toolPanel->setParent(this);
    }
    toolPanel->setParentNotify(this);
    
    // Rearrange panels to make room?
    if (!(position & CLIENT_NOTMANAGED)) {
        // Count panels on this side and note the last one
        int count = 0;
        int last = 0;
        int pos;
        for (pos = 1; pos < (int)clientArea.size(); ++pos) {
            if ((clientArea[pos].window) && (!(clientArea[pos].position & CLIENT_NOTMANAGED)) && ((clientArea[pos].position & CLIENT_GETSIDE) == (position & CLIENT_GETSIDE))) {
                count++;
                last = pos;
            }
        }

        // Assuming we're not the only panel on this size, we need to manage the size
        // If we are, we'll just end up resizing at worst
        int totalSize;
        int prevSize = 0;
        
        // Desired size is the lesser of requested size or our fair share of the panel
        // Also calculate the total size of all previous items on this panel
        if (position & CLIENT_TOPBOTTOM) {
            desiredSize = toolPanel->getWidth();
            totalSize = width - (outerBorder * 2);
            if (count) {
                // Base off of requested size- not actual
                prevSize = clientArea[last].x - outerBorder + clientArea[last].width;
            }
        }
        else {
            desiredSize = toolPanel->getHeight();
            totalSize = height - titlebarY - outerBorder - titlebarHeight - reserve[CLIENT_TOP] - reserve[CLIENT_BOTTOM];
            if (count) {
                prevSize = clientArea[last].y - titlebarY - titlebarHeight - reserve[CLIENT_TOP] + clientArea[last].height;
            }
        }

        // Apply any empty space to our "requirement"
        desiredSize -= totalSize - prevSize;

        // If more room needed, we can try resizing
        if (desiredSize > 0) {
            if (position & CLIENT_TOPBOTTOM) {
                int oldSize = newWidth;
                newWidth += desiredSize;
                confineSize(newX, newY, newWidth, newHeight);
                desiredSize -= newWidth - oldSize;
            }
            else {
                int oldSize = newHeight;
                newHeight += desiredSize;
                confineSize(newX, newY, newWidth, newHeight);
                desiredSize -= newHeight - oldSize;
            }
        }
        
        // Limit any remaining desired size to our fair share of the previous space
        if (desiredSize > totalSize / (count + 1)) {
            desiredSize = totalSize / (count + 1);
        }
        
        // Is more room still needed?
        if ((desiredSize > 0) && (count)) {
            int totalDesiredSize = desiredSize;
            // Rearrange all other clients equally to make room
            for (pos = 1; pos < (int)clientArea.size(); ++pos) {
                if ((clientArea[pos].window) && (!(clientArea[pos].position & CLIENT_NOTMANAGED)) && ((clientArea[pos].position & CLIENT_GETSIDE) == (position & CLIENT_GETSIDE))) {
                    if (position & CLIENT_TOPBOTTOM) {
                        // Decrease size by an amount based on ratio of total size
                        clientArea[pos].width -= totalDesiredSize * clientArea[pos].width / totalSize;
                        desiredSize -= totalDesiredSize * clientArea[pos].width / totalSize;
                    }
                    else {
                        // Decrease size by an amount based on ratio of total size
                        clientArea[pos].height -= totalDesiredSize * clientArea[pos].height / totalSize;
                        desiredSize -= totalDesiredSize * clientArea[pos].height / totalSize;
                    }
                }
            }
        }
        
        // If any "desired size" left, deduct it from what's allowed
        if (position & CLIENT_TOPBOTTOM) {
            desiredSize = toolPanel->getWidth() - ((desiredSize > 0) ? desiredSize : 0);
        }
        else {
            desiredSize = toolPanel->getHeight() - ((desiredSize > 0) ? desiredSize : 0);
        }

        // Resize window if new panel is docked and larger than the reserve already there
        if (state != FRAME_STATE_MAXIMIZED) {
            if ((position & CLIENT_GETSIDE) == CLIENT_LEFT) {
                if (toolPanel->getWidth() > reserve[CLIENT_LEFT]) {
                    newWidth += toolPanel->getWidth() - reserve[CLIENT_LEFT];
                    if (reserve[CLIENT_LEFT] == 0) newWidth += FRAME_CLIENTPAD;
                }
            }
            if ((position & CLIENT_GETSIDE) == CLIENT_RIGHT) {
                if (toolPanel->getWidth() > reserve[CLIENT_RIGHT]) {
                    newWidth += toolPanel->getWidth() - reserve[CLIENT_RIGHT];
                    if (reserve[CLIENT_LEFT] == 0) newWidth += FRAME_CLIENTPAD;
                }
            }
            if ((position & CLIENT_GETSIDE) == CLIENT_TOP) {
                if (toolPanel->getHeight() > reserve[CLIENT_TOP]) {
                    newHeight += toolPanel->getHeight() - reserve[CLIENT_TOP];
                    if (reserve[CLIENT_LEFT] == 0) newWidth += FRAME_CLIENTPAD;
                }
            }
            if ((position & CLIENT_GETSIDE) == CLIENT_BOTTOM) {
                if (toolPanel->getHeight() > reserve[CLIENT_BOTTOM]) {
                    newHeight += toolPanel->getHeight() - reserve[CLIENT_BOTTOM];
                    if (reserve[CLIENT_LEFT] == 0) newWidth += FRAME_CLIENTPAD;
                }
            }

            confineSize(newX, newY, newWidth, newHeight);
            move(newX, newY);
        }
    }

    // Add to panels
    ClientArea ca = { toolPanel, -1, -1,
                      (position & CLIENT_TOPBOTTOM) ? desiredSize : -1,
                      (position & CLIENT_TOPBOTTOM) ? -1 : desiredSize,
                      -1, -1, position };
                      
    if ((insertBefore) && ((*insertBefore) != clientArea.end())) {
        clientArea.insert(*insertBefore, ca);
    }
    else {
        clientArea.push_back(ca);
    }
    
    // Handles resize and also rearranges client areas
    resize(newWidth, newHeight);
    if (wasMinimized) toolPanel->minimize();

    return;
}

void FrameWindow::removeToolPanel(Window* toolPanel) { start_func
    assert(toolPanel);
    assert(clientArea[0].window != toolPanel);

    vector<ClientArea>::iterator end = clientArea.end();
    for (vector<ClientArea>::iterator pos = clientArea.begin(); pos != end; ++pos) {
        if ((*pos).window == toolPanel) {
            (*pos).window->setParent(NULL);
            clientArea.erase(pos);
            break;
        }
    }
    
    // @TODO: Do we need to check if toolpanel is referenced in a drag setting, subfocus, etc?
    
    // Rearranges client areas as needed
    resize(width, height);
}


void FrameWindow::setTitle(const string& newTitle) { start_func
    // (this line could memfault)
    titlebar = newTitle;
    setDirty();
    whyDirty |= FRAME_DIRTY_TITLE;
}

Window::WindowType FrameWindow::windowType() const { start_func
    return WINDOW_FRAME;
}

#ifndef NDEBUG
const char* FrameWindow::debugDump() const { start_func
    return titlebar.c_str();
}
#endif

void FrameWindow::nextWindow() { start_func
    // Cancel if modal or not on desktop
    if ((windowSort() == WINDOWSORT_MODAL) || (parent != desktop)) return;

    desktop->bringToTop(desktop->findWindow(WINDOW_FRAME, 0));
}

void FrameWindow::prevWindow() { start_func
    // Cancel if modal or not on desktop
    if ((windowSort() == WINDOWSORT_MODAL) || (parent != desktop)) return;

    // Find us
    int pos = 0;
    while (desktop->findWindow(WINDOW_FRAME, pos) != this) {
        // (Shouldn't be NULL unless we aren't in collection!)
        assert(desktop->findWindow(WINDOW_FRAME, pos));
        ++pos;
    }
    
    if (--pos >= 0) {
        desktop->bringToTop(desktop->findWindow(WINDOW_FRAME, pos));
        desktop->sendToBottom(this);
    }
}

int FrameWindow::isMinimized() { start_func
    return (state == FRAME_STATE_MINIMIZED) ? 1 : 0;
}

void FrameWindow::minimize() { start_func
    if (state == FRAME_STATE_MINIMIZED) return;

    char* button = strchr(titlebarButtons, 'h');
    if (!button) return; // Can't minimize!

    if (state == FRAME_STATE_MAXIMIZED) restore();

    restoreX = x;
    restoreY = y;
    restoreWidth = width;
    restoreHeight = height;

    state = FRAME_STATE_MINIMIZED;
    resize(width, titlebarHeight + 2 * outerBorder);

    *button = 'j';
}

void FrameWindow::maximize() { start_func
    if ((state == FRAME_STATE_MAXIMIZED) || ((parent != desktop) && (parent != NULL))) return;

    char* button = strchr(titlebarButtons, 'g');
    if (!button) return; // Can't maximize!

    if (state == FRAME_STATE_MINIMIZED) {
        char* button2 = strchr(titlebarButtons, 'j');
        if (button2) *button2 = 'h';
    }

    restoreX = x;
    restoreY = y;
    
    if (state == FRAME_STATE_NORMAL) {
        restoreWidth = width;
        restoreHeight = height;
    }

    // (final -1/+1 is for the inner titlebar border)
    state = FRAME_STATE_MAXIMIZED;
    move(desktop->desktopX() - outerBorder, desktop->desktopY() - outerBorder - 1);
    resize(desktop->desktopWidth() + outerBorder * 2,
           desktop->desktopHeight() + outerBorder * 2 + 1);

    *button = 'j';
}

void FrameWindow::restore() { start_func
    if (state == FRAME_STATE_NORMAL) return;
    
    char* button = strchr(titlebarButtons, 'j');
    if (button) *button = state == FRAME_STATE_MAXIMIZED ? 'g' : 'h';

    if (state != FRAME_STATE_MAXIMIZED) {
        restoreX = x;
        restoreY = y;
    }

    state = FRAME_STATE_NORMAL;
    confineSize(restoreX, restoreY, restoreWidth, restoreHeight);
    move(restoreX, restoreY);
    resize(restoreWidth, restoreHeight);
}

void FrameWindow::toggleToolPanel(int index, int giveFocusIfOpen) { start_func
    if ((index < (int)clientArea.size()) && (index > 0) && (clientArea[index].window)) {
        int position = clientArea[index].position & ~CLIENT_HIDDEN;
        
        // Ignore floating/desktop (@TODO: fix later)
        if ((position & CLIENT_FLOAT) || (position & CLIENT_DOCKED)) {
            return;
        }
        
        // Toggle closed or not
        position ^= CLIENT_CLOSED;
        
        if ((!(position & CLIENT_CLOSED)) && (giveFocusIfOpen)) focusToolPanel(index);
        else if ((position & CLIENT_CLOSED) && (subFocus == index)) focusToolPanel(0);
        
        // Redraw
        resize(width, height);
    }
}

void FrameWindow::focusToolPanel(int index) { start_func
    // No focus if not visible or doesn't exist
    if (index >= (int)clientArea.size()) index = 0;
    if (index > 0) {
        if ((!clientArea[index].window) || (clientArea[index].position & CLIENT_NOTMANAGED)) {
            index = 0;
        }

        // Ignore floating/desktop (@TODO: fix later)
        if ((clientArea[index].position & CLIENT_FLOAT) || (clientArea[index].position & CLIENT_DOCKED)) {
            return;
        }
    }
    // Doesn't match current focus?
    if ((index != subFocus) && (haveFocus)) {
        // Lose
        SDL_Event customEvent;
        customEvent.type = SDL_INPUTFOCUS;
        customEvent.user.code = 2;
        customEvent.user.data1 = NULL;
        customEvent.user.data2 = NULL;
        if (clientArea[subFocus].window) clientArea[subFocus].window->event(0, &customEvent);
        
        // Gain
        subFocus = index;
        customEvent.user.code = 1;
        if (clientArea[subFocus].window) clientArea[subFocus].window->event(haveFocus, &customEvent);
    }
    // If we didn't have focus, we still need to set our personal focus
    // for when we regain focus later
    else {
        subFocus = index;
    }
}

void FrameWindow::redoSize() { start_func
    if (state == FRAME_STATE_MAXIMIZED) {
        restore();
        maximize();
    }
    else {
        int cX = x;
        int cY = y;
        int cW = width;
        int cH = height;

        confineSize(cX, cY, cW, cH);
        move(cX, cY);
        resize(cW, cH);
    }
}

void FrameWindow::updateClientSize() { start_func
    resize(width, height);
}

void FrameWindow::setAutoCenter(int newAutoCenter) { start_func
    autoCenterScroll = newAutoCenter;
    resize(width, height);
}

void FrameWindow::setScroll(int vLine, int hLine) { start_func
    assert(vLine > 0);
    assert(hLine > 0);
    
    vScrollLine = vLine;
    hScrollLine = hLine;
    
    // A page = full height/width, rounded down to a line multiple,
    // minus one line's worth
    vScrollPage = (clientHeightInner / vLine - 1) * vLine;
    if (vScrollPage < vLine) vScrollPage = vLine;
    hScrollPage = (clientWidthInner / hLine - 1) * hLine;
    if (hScrollPage < hLine) hScrollPage = hLine;
}

void FrameWindow::resize(int newWidth, int newHeight, int newViewWidth, int newViewHeight, int fromParent) { start_func
    // If a framepanel, resize to view if from parent (unless minimized)
    if (framePanel) {
        if ((state != FRAME_STATE_MINIMIZED) && (fromParent)) {
            if (newViewWidth == -1) newViewWidth = viewWidth;
            if (newViewWidth != -1) newWidth = newViewWidth;
            if (newViewHeight == -1) newViewHeight = viewHeight;
            if (newViewHeight != -1) newHeight = newViewHeight;
        }
    }
    else {
        // (enforce minimum width/height)
        if (newHeight < (titlebarHeight + 2 * outerBorder)) {
            newHeight = titlebarHeight + 2 * outerBorder;
        }
        if (newWidth < (outerBorder * 2 + 2 + titlebarButtonTotalWidth)) {
            newWidth = outerBorder * 2 + 2 + titlebarButtonTotalWidth;
        }
    }

    if (clientArea[0].window) {
        clientActualWidth = clientArea[0].window->getWidth();
        clientActualHeight = clientArea[0].window->getHeight();
    }
    else {
        clientActualWidth = clientActualHeight = 0;
    }

    // Our size (doesn't always set dirty, so we must)
    Window::resize(newWidth, newHeight, newViewWidth, newViewHeight, fromParent);
    setDirty();
    whyDirty = FRAME_DIRTY_ALL;

    // Tool panel and reserve sizes and client area size
    reorganizeReserves();

    // If resizing in SNAP mode-
    // Our actual size should fit exactly to hold clientarea 0, if possible- but
    // allow it to resize first (unless we're minimized)
    if ((resizable == RESIZING_SNAP) &&
        (clientArea[0].window) &&
        (clientActualWidth > 0) &&
        (clientActualHeight > 0) &&
        (state != FRAME_STATE_MINIMIZED)) {

        clientArea[0].window->resize(clientActualWidth, clientActualHeight, clientArea[0].width, clientArea[0].height, 1);
        clientActualWidth = clientArea[0].window->getWidth();
        clientActualHeight = clientArea[0].window->getHeight();
        if ((clientActualWidth != clientArea[0].width) || (clientActualHeight != clientArea[0].height)) {
            newWidth += clientActualWidth - clientArea[0].width;
            newHeight += clientActualHeight - clientArea[0].height;
            Window::resize(newWidth, newHeight, newViewWidth, newViewHeight, fromParent);
            reorganizeReserves();
        }
    }

    // Finish tool panels
    int max = clientArea.size();
    for (int pos = 1; pos < max; ++pos) {
        if (clientArea[pos].window) {
            if ((clientArea[pos].position & CLIENT_FLOAT) || (clientArea[pos].position & CLIENT_DOCKED)) {
                // Don't resize floating or desktop-docked panels
            }
            else if (clientArea[pos].position & CLIENT_NOTMANAGED) {
                clientArea[pos].window->resize(clientArea[pos].window->getWidth(), clientArea[pos].window->getHeight(), 0, 0, 1);
            }
            else {
                clientArea[pos].window->move(clientArea[pos].x, clientArea[pos].y);
                clientArea[pos].window->resize(clientArea[pos].window->getWidth(), clientArea[pos].window->getHeight(), clientArea[pos].widthShown, clientArea[pos].heightShown, 1);
            }
        }
    }

    // Scrollbars default to off    
    vScrollbarX = vScrollbarY = vScrollbarWidth = vScrollbarHeight = vScrollbarTabSize = 0;
    hScrollbarX = hScrollbarY = hScrollbarWidth = hScrollbarHeight = hScrollbarTabSize = 0;

    // Must have room for at least 3 button sizes, or no scrollbars appear
    if ((clientArea[0].window) &&
        (clientArea[0].width >= scrollbarButtonSize * 3) &&
        (clientArea[0].height >= scrollbarButtonSize * 3)) {

        int needH = clientActualWidth > clientArea[0].width;
        int needV = clientActualHeight > clientArea[0].height;
        if (needV) needH = clientActualWidth > clientArea[0].width - scrollbarButtonSize;
        if (needH) needV = clientActualHeight > clientArea[0].height - scrollbarButtonSize;
        
        if (needV) {
            vScrollbarX = clientArea[0].x + clientArea[0].width - scrollbarButtonSize;
            vScrollbarY = clientArea[0].y;
            vScrollbarWidth = scrollbarButtonSize;
            vScrollbarHeight = clientArea[0].height;
            if (needH) vScrollbarHeight -= scrollbarButtonSize;
        }
        
        if (needH) {
            hScrollbarX = clientArea[0].x;
            hScrollbarY = clientArea[0].y + clientArea[0].height - scrollbarButtonSize;
            hScrollbarWidth = clientArea[0].width;
            hScrollbarHeight = scrollbarButtonSize;
            if (needV) hScrollbarWidth -= scrollbarButtonSize;
        }
    }
    
    // Assign inner size
    clientWidthInner = clientArea[0].width - vScrollbarWidth;
    clientHeightInner = clientArea[0].height - hScrollbarHeight;
    
    // Now we can determine scrollbar tab sizes
    // Determine tab size as the percent the current view is of the total view
    // Tab position determined by scrollTo call later
    if (vScrollbarX) {
        vScrollbarTabSize = clientHeightInner * (vScrollbarHeight - scrollbarButtonSize * 2) / clientActualHeight;
        if (vScrollbarTabSize < FRAME_SCROLLTABMIN) vScrollbarTabSize = FRAME_SCROLLTABMIN;
        if (vScrollbarTabSize > (vScrollbarHeight - scrollbarButtonSize * 2)) vScrollbarTabSize = 0;
    }

    if (hScrollbarY) {
        hScrollbarTabSize = clientWidthInner * (hScrollbarWidth - scrollbarButtonSize * 2) / clientActualWidth;
        if (hScrollbarTabSize < FRAME_SCROLLTABMIN) hScrollbarTabSize = FRAME_SCROLLTABMIN;
        if (hScrollbarTabSize > (hScrollbarWidth - scrollbarButtonSize * 2)) hScrollbarTabSize = 0;
    }

    // Calculate titlebar size
    titlebarWidth = newWidth - (outerBorder * 2);
    titlebarButtonX = titlebarX + titlebarWidth - 1 - titlebarButtonTotalWidth;

    // Ensure we're scrolled properly and sizing is set right
    if ((autoCenterScroll) && (clientActualWidth < clientWidthInner)) {
        scrollX = (clientWidthInner - clientActualWidth) / 2;
    }
    if ((autoCenterScroll) && (clientActualHeight < clientHeightInner)) {
        scrollY = (clientHeightInner - clientActualHeight) / 2;
    }
    scrollTo(scrollX, scrollY);
    setScroll(vScrollLine, hScrollLine);

    // Tell client of new view size?
    if (clientArea[0].window) {
        clientArea[0].window->resize(clientActualWidth, clientActualHeight, clientWidthInner, clientHeightInner, 1);
        clientArea[0].window->move(clientArea[0].x + scrollX, clientArea[0].y + scrollY);
    }
}

FrameWindow::~FrameWindow() { start_func
    if (parent == desktop) desktop->removeWindow(this);
    
    vector<ClientArea>::iterator end = clientArea.end();
    for (vector<ClientArea>::iterator pos = clientArea.begin(); pos != end; ++pos) {
        if ((*pos).window) {
            (*pos).window->setParent(NULL);
            (*pos).window->setParentNotify(NULL);
            if ((*pos).window->wantsToBeDeleted()) delete (*pos).window;
        }
    }
}

void FrameWindow::resolutionChange(int fromW, int fromH, int fromBpp, int toW, int toH, int toBpp) { start_func
    Window::resolutionChange(fromW, fromH, fromBpp, toW, toH, toBpp);

    vector<ClientArea>::iterator end = clientArea.end();
    for (vector<ClientArea>::iterator pos = clientArea.begin(); pos != end; ++pos) {
        if ((*pos).window) {
            (*pos).window->resolutionChange(fromW, fromH, fromBpp, toW, toH, toBpp);
        }
    }
}


void FrameWindow::dragFloatPanel(int index, int drop) { start_func
    assert(index > 0);
    assert(index < (int)clientArea.size());
    
    if ((clientArea[index].window) && (clientArea[index].position & CLIENT_FLOAT)) {
        // x/y position of floating panel, relative to our window
        int fx = clientArea[index].window->getX() - x;
        int fy = clientArea[index].window->getY() - y;

        // sensitivity based off size
        int sx = clientArea[index].window->getWidth() / 2;
        int sy = clientArea[index].window->getHeight() / 2;
        
        // work off of center
        fx += sx;
        fy += sy;
        
        // scan all panel positions
        int closestDist = 99999;
        int closestSide = -1;
        int closestAfter = -1;
        int dist;
        vector<ClientArea>::iterator source = clientArea.end();
        vector<ClientArea>::iterator closest = clientArea.end();
        vector<ClientArea>::iterator end = clientArea.end();
        vector<ClientArea>::iterator pos = clientArea.begin();
        for (++pos; pos != end; ++pos) {
            if ((*pos).window == clientArea[index].window) {
                source = pos;
            }
            else if (((*pos).window) && (!((*pos).position & CLIENT_NOTMANAGED))) {
                if ((*pos).position & CLIENT_TOPBOTTOM) {
                    // Look at left and right centers
                    if (abs(fy - ((*pos).y + (*pos).heightShown / 2)) < sy) {
                        if (abs(fx - (*pos).x) < sx) {
                            dist = abs(fx - (*pos).x) + abs(fy - ((*pos).y + (*pos).heightShown / 2));
                            if (dist <= closestDist) {
                                closestDist = dist;
                                closest = pos;
                                closestAfter = 0;
                                floatTargetX = (*pos).x;
                            }
                        }
                        if (abs(fx - ((*pos).x + (*pos).widthShown)) < sx) {
                            dist = abs(fx - ((*pos).x + (*pos).widthShown)) + abs(fy - ((*pos).y + (*pos).heightShown / 2));
                            if (dist <= closestDist) {
                                closestDist = dist;
                                closest = pos;
                                closestAfter = 1;
                                floatTargetX = (*pos).x + (*pos).widthShown;
                            }
                        }
                        if (closest == pos) {
                            closestSide = (*pos).position;
                            floatTargetX -= sx;
                            floatTargetWidth = sx * 2;
                            floatTargetY = (*pos).y;
                            floatTargetHeight = (*pos).heightShown;
                        }
                    }
                }
                else {
                    // Look at top and bottom centers
                    if (abs(fx - ((*pos).x + (*pos).widthShown / 2)) < sx) {
                        if (abs(fy - (*pos).y) < sy) {
                            dist = abs(fy - (*pos).y) + abs(fx - ((*pos).x + (*pos).widthShown / 2));
                            if (dist <= closestDist) {
                                closestDist = dist;
                                closest = pos;
                                closestAfter = 0;
                                floatTargetY = (*pos).y;
                            }
                        }
                        if (abs(fy - ((*pos).y + (*pos).heightShown)) < sy) {
                            dist = abs(fy - ((*pos).y + (*pos).heightShown)) + abs(fx - ((*pos).x + (*pos).widthShown / 2));
                            if (dist <= closestDist) {
                                closestDist = dist;
                                closest = pos;
                                closestAfter = 1;
                                floatTargetY = (*pos).y + (*pos).heightShown;
                            }
                        }
                        if (closest == pos) {
                            closestSide = (*pos).position;
                            floatTargetY -= sy;
                            floatTargetHeight = sy * 2;
                            floatTargetX = (*pos).x;
                            floatTargetWidth = (*pos).widthShown;
                        }
                    }
                }
            }
        }
        
        // Scan any empty reserves also
        if (reserve[CLIENT_LEFT] <= 0) {
            // Look at left center of client area
            if ((abs(fy - (clientArea[0].y + clientArea[0].height / 2)) < sy) && (abs(fx - clientArea[0].x) < sx)) {
                dist = abs(fy - (clientArea[0].y + clientArea[0].height / 2)) + abs(fx - clientArea[0].x);
                if (dist <= closestDist) {
                    closestDist = dist;
                    closestSide = CLIENT_LEFT;
                    closest = clientArea.end();
                    floatTargetX = clientArea[0].x - sx;
                    floatTargetY = clientArea[0].y;
                    floatTargetWidth = sx * 2;
                    floatTargetHeight = clientArea[0].height;
                }
            }
        }
        if (reserve[CLIENT_RIGHT] <= 0) {
            // Look at right center of client area
            if ((abs(fy - (clientArea[0].y + clientArea[0].height / 2)) < sy) && (abs(fx - (clientArea[0].x + clientArea[0].width)) < sx)) {
                dist = abs(fy - (clientArea[0].y + clientArea[0].height / 2)) + abs(fx - (clientArea[0].x + clientArea[0].width));
                if (dist <= closestDist) {
                    closestDist = dist;
                    closestSide = CLIENT_RIGHT;
                    closest = clientArea.end();
                    floatTargetX = clientArea[0].x + clientArea[0].width - sx;
                    floatTargetY = clientArea[0].y;
                    floatTargetWidth = sx * 2;
                    floatTargetHeight = clientArea[0].height;
                }
            }
        }
        if (reserve[CLIENT_TOP] <= 0) {
            // Look at top center of client area
            if ((abs(fx - (clientArea[0].x + clientArea[0].width / 2)) < sx) && (abs(fy - clientArea[0].y) < sy)) {
                dist = abs(fx - (clientArea[0].x + clientArea[0].width / 2)) + abs(fy - clientArea[0].y);
                if (dist <= closestDist) {
                    closestDist = dist;
                    closestSide = CLIENT_TOP;
                    closest = clientArea.end();
                    floatTargetX = clientArea[0].x;
                    floatTargetY = clientArea[0].y - sy;
                    floatTargetWidth = clientArea[0].width;
                    floatTargetHeight = sy * 2;
                }
            }
        }
        if (reserve[CLIENT_BOTTOM] <= 0) {
            // Look at bottom center of client area
            if ((abs(fx - (clientArea[0].x + clientArea[0].width / 2)) < sx) && (abs(fy - (clientArea[0].y + clientArea[0].height)) < sy)) {
                dist = abs(fx - (clientArea[0].x + clientArea[0].width / 2)) + abs(fy - (clientArea[0].y + clientArea[0].height));
                if (dist <= closestDist) {
                    closestDist = dist;
                    closestSide = CLIENT_BOTTOM;
                    closest = clientArea.end();
                    floatTargetX = clientArea[0].x;
                    floatTargetY = clientArea[0].y + clientArea[0].height - sy;
                    floatTargetWidth = clientArea[0].width;
                    floatTargetHeight = sy * 2;
                }
            }
        }

        if ((closestSide < 0) || (floatTargetWidth <= 0) || (floatTargetHeight <= 0)) {
            floatTarget = 0;
        }
        else {
            if (drop) {
                // Drop panel on this side
                FrameWindow* window = dynamic_cast<FrameWindow*>((*source).window);
                int position = (((*source).position & ~CLIENT_FLOAT) & ~CLIENT_GETSIDE) | closestSide;
                
                // If relative to an existing panel, sort into list in proper position
                if (closest != clientArea.end()) {
                    // adjust element later in list first, to preserve iterators
                    if (source > closest) {
                        clientArea.erase(source);
                        if (closestAfter) ++closest; // (source = closest now? still ok)
                        addToolPanel(window, position, &closest);
                    }
                    else {
                        (*source).window = NULL;
                        if (closestAfter) ++closest;
                        addToolPanel(window, position, &closest);
                        clientArea.erase(source);
                    }
                    
                    // Note that both iterators are likely invalid now
                }
                else {
                    clientArea.erase(source);
                    addToolPanel(window, position);
                }

                floatTarget = 0;
                resize(width, height, -1, -1);
            }
            else {
                floatTarget = 1;
            }
        }
        
        setDirty();
        whyDirty = FRAME_DIRTY_ALL;
    }
}

void FrameWindow::dragToolPanel(int index, int xMotion, int yMotion, int& dragItemOverflowX, int& dragItemOverflowY) { start_func
    assert(index > 0);
    assert(index < (int)clientArea.size());

    xMotion += dragItemOverflowX;
    dragItemOverflowX = 0;
    yMotion += dragItemOverflowY;
    dragItemOverflowY = 0;
    
    if ((clientArea[index].window) && (!(clientArea[index].position & CLIENT_NOTMANAGED))) {
        // Dragging off of toolbar? (drag 1/2 of distance perpendicular)
        int dragOff = 0;
        if (clientArea[index].position & CLIENT_TOPBOTTOM) {
            if (abs(yMotion) > clientArea[index].heightShown / 2) {
                dragOff = 1;
            }
        }
        else {
            if (abs(xMotion) > clientArea[index].widthShown / 2) {
                dragOff = 1;
            }
        }
        
        if (dragOff) {
            // Target desktop position
            int tx, ty;
            tx = x + clientArea[index].x + xMotion - FRAME_OUTERBORDER;
            ty = y + clientArea[index].y + yMotion - FRAME_OUTERBORDER;
            
            // Float it
            int newX = tx;
            int newY = ty;
            int newWidth = dynamic_cast<FrameWindow*>(clientArea[index].window)->getWidth() + FRAME_OUTERBORDER * 2;
            int newHeight = dynamic_cast<FrameWindow*>(clientArea[index].window)->getHeight() + FRAME_OUTERBORDER * 2;
            dynamic_cast<FrameWindow*>(clientArea[index].window)->setFeatures(RESIZING_NOCHANGE, FRAMETYPE_NOCHANGE, TITLEBAR_NOCHANGE, 1, this);
            dynamic_cast<FrameWindow*>(clientArea[index].window)->setParent(NULL);
            dynamic_cast<FrameWindow*>(clientArea[index].window)->confineSize(newX, newY, newWidth, newHeight);
            clientArea[index].position |= CLIENT_FLOAT;
            dynamic_cast<FrameWindow*>(clientArea[index].window)->show(newX, newY, newWidth, newHeight);
            resize(width, height, -1, -1);
            
            // Initiate a fake click to continue drag- click must be on exact
            // same relative location within window so determine how far off that is
            tx = dynamic_cast<FrameWindow*>(clientArea[index].window)->getX() - tx;
            ty = dynamic_cast<FrameWindow*>(clientArea[index].window)->getY() - ty;
            
            // Generate fake event
            SDL_Event customEvent;
            int mx, my;
            SDL_GetMouseState(&mx, &my);
            customEvent.button.x = mx + tx;
            customEvent.button.y = my + ty;
            customEvent.type = SDL_MOUSEBUTTONDOWN;
            customEvent.button.button = SDL_BUTTON_LEFT;
            desktop->preventDoubleClick();
            insertEvent(&customEvent);
    
            return;
        }
        
        // Find panel right before us
        // Set sizes of panels before us "in stone" (once we start dragging,
        // the user has locked in the previous sizes as a starting point)
        int previous = 0;
        for (int pos = 1; pos < index; ++pos) {
            if (clientArea[pos].window) {
                if (clientArea[pos].position == clientArea[index].position) {
                    if (clientArea[pos].position& CLIENT_TOPBOTTOM) {
                        clientArea[pos].width = clientArea[pos].widthShown;
                    }
                    else {
                        clientArea[pos].height = clientArea[pos].heightShown;
                    }
                    previous = pos;
                }
            }
        }

        // If found, resize it appropriately
        if (previous) {
            int oldSize;
            
            if (clientArea[index].position & CLIENT_TOPBOTTOM) {
                clientArea[previous].width += xMotion;
                oldSize = clientArea[previous].widthShown + xMotion;
                xMotion = 0;
            }
            else {
                clientArea[previous].height += yMotion;
                oldSize = clientArea[previous].heightShown + yMotion;
                yMotion = 0;
            }

            resize(width, height, -1, -1);

            if (clientArea[index].position & CLIENT_TOPBOTTOM) {
                if (oldSize != clientArea[previous].widthShown) {
                    dragItemOverflowX = oldSize - clientArea[previous].widthShown;
                }
            }
            else {
                if (oldSize != clientArea[previous].heightShown) {
                    dragItemOverflowY = oldSize - clientArea[previous].heightShown;
                }
            }
        }
    }
    
    dragItemOverflowX = xMotion;
    dragItemOverflowY = yMotion;
}

void FrameWindow::childMoved(int fromX, int fromY, int toX, int toY, Window* child) { start_func
    assert(child);
    
    setChildDirty();
}

void FrameWindow::childResized(int fromW, int fromH, int toW, int toH, Window* child) { start_func
    assert(child);

    // Reaccount for child's new window size as requested
    for (unsigned int pos = 1; pos < clientArea.size(); ++pos) {
        if ((clientArea[pos].window == child) && (!(clientArea[pos].position & CLIENT_NOTMANAGED))) {
            clientArea[pos].width = -1;
            clientArea[pos].height = -1;
            resize(width, height, -1, -1);
            break;
        }
    }
        
    setChildDirty();
}

void FrameWindow::childDeleted(Window* child) { start_func
    assert(child);
    
    vector<ClientArea>::iterator end = clientArea.end();
    for (vector<ClientArea>::iterator pos = clientArea.begin(); pos != end; ++pos) {
        if ((*pos).window == child) {
            (*pos).window= NULL;
            break;
        }
    }
}

void FrameWindow::childModified(Window* child) { start_func
    assert(child);

    vector<ClientArea>::iterator end = clientArea.end();
    for (vector<ClientArea>::iterator pos = clientArea.begin(); pos != end; ++pos) {
        if (((*pos).window) && ((*pos).window != child)) {
            (*pos).window->siblingModified(child);
        }
    }
    
    Window::childModified(child);
}

void FrameWindow::siblingModified(Window* sibling) { start_func
    assert(sibling);

    vector<ClientArea>::iterator end = clientArea.end();
    for (vector<ClientArea>::iterator pos = clientArea.begin(); pos != end; ++pos) {
        if (((*pos).window) && ((*pos).window != sibling)) {
            (*pos).window->siblingModified(sibling);
        }
    }
}

void FrameWindow::display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset) { start_func
    assert(destSurface);

    if (visible) {
        // This breaks redraw down into titlebar, scrollbars, edge, client areas
        // Redraw all?
        if (whyDirty == FRAME_DIRTY_ALL) {
            getRect(toDisplay);
            toDisplay.x += xOffset;
            toDisplay.y += yOffset;
            dirty = 0;
            intersectRects(toDisplay, clipArea);
        }
        
        xOffset += x;
        yOffset += y;
        
        // Anything to draw so far?
        if (toDisplay.w) {
            SDL_SetClipRect(destSurface, &toDisplay);
        
            // Draw fill and border
            if (outerBorder) {
                drawGuiBox(xOffset, yOffset, width, height, FRAME_BEVELTHICKNESS, destSurface);
            }
            else {
                drawRect(xOffset, yOffset, width, height, guiPacked[COLOR_FILL], destSurface);
            }
            if (((frameType == FRAMETYPE_BEVEL_BK) || (frameType == FRAMETYPE_BEVEL_TEXT)) &&
                (clientArea[0].width > 0) && (clientArea[0].height > 0)) {
                drawGuiBoxInvert(xOffset + clientArea[0].x - FRAME_BEVELTHICKNESS, yOffset + clientArea[0].y - FRAME_BEVELTHICKNESS,
                                 clientArea[0].width + FRAME_BEVELTHICKNESS * 2, clientArea[0].height + FRAME_BEVELTHICKNESS * 2,
                                 FRAME_BEVELTHICKNESS, destSurface);
            }
        }
        
        // This will be the final displayed amount, we update it as we go but still work
        // off of the original dirtied area for efficiency
        Rect updatedArea = toDisplay;
        
        // Titlebar
        if (titlebarType != TITLEBAR_OFF) {
            // Determine clip area for titlebar
            Rect titleClip = { titlebarX + xOffset, titlebarY + yOffset, titlebarWidth, titlebarHeight };
            intersectRects(titleClip, clipArea);
    
            // If we need to update title, OR it intersects with the dirty area,
            // redraw it; note that if the second conditional is tested, vertClip will be reduced
            // to whatever portion intersects with the dirty area, which is what we want
            if ((whyDirty & FRAME_DIRTY_TITLE) || (intersectRects(titleClip, toDisplay))) {
                // Add titlebar rect into updated area
                boundRects(updatedArea, titleClip);
    
                SDL_SetClipRect(destSurface, &titleClip);
                drawGradient(titlebarX + xOffset, titlebarY + yOffset, titlebarWidth - titlebarButtonTotalWidth, titlebarHeight, guiRGB[haveFocus ? COLOR_TITLEACTIVE1 : COLOR_TITLEINACTIVE1], guiRGB[haveFocus ? COLOR_TITLEACTIVE2 : COLOR_TITLEINACTIVE2], destSurface);
                // (fill corners of inverted titlebar box first)
                drawPixel(titlebarX + xOffset + titlebarWidth - 1, titlebarY + yOffset, guiPacked[COLOR_FILL], destSurface);
                drawPixel(titlebarX + xOffset, titlebarY + yOffset + titlebarHeight - 1, guiPacked[COLOR_FILL], destSurface);
                drawGuiBoxInvert(titlebarX + xOffset, titlebarY + yOffset, titlebarWidth, titlebarHeight, 1, destSurface);
                
                // Buttons
                for (int pos = strlen(titlebarButtons) - 1; pos >= 0; --pos) {
                    string text(1, titlebarButtons[pos]);
                    if ((dragMode == FRAME_DRAG_BUTTONS) && (dragItem == (DragSubType)(pos + 1))) {
                        // Non-gradient: drawRect(xOffset + titlebarButtonX + pos * titlebarButtonSize, yOffset + titlebarButtonY, titlebarButtonSize, titlebarButtonSize, guiPacked[COLOR_FILL], destSurface);
                        drawGradient(xOffset + titlebarButtonX + pos * titlebarButtonSize, yOffset + titlebarButtonY, titlebarButtonSize, titlebarButtonSize, guiRGB[COLOR_BUTTONDOWN1], guiRGB[COLOR_BUTTONDOWN2], destSurface);
                        drawGuiBoxInvert(xOffset + titlebarButtonX + pos * titlebarButtonSize, yOffset + titlebarButtonY, titlebarButtonSize, titlebarButtonSize, 2, destSurface);
                        drawText(text, guiRGB[COLOR_TEXT], xOffset + titlebarButtonX + pos * titlebarButtonSize + titlebarButtonXPad + 1, yOffset + titlebarButtonY + titlebarButtonXPad + 1, destSurface, titlebarType == TITLEBAR_TOOL ? FONT_TOOLTITLEBARWIDGET : FONT_TITLEBARWIDGET);
                    }
                    else {
                        drawGuiBox(xOffset + titlebarButtonX + pos * titlebarButtonSize, yOffset + titlebarButtonY, titlebarButtonSize, titlebarButtonSize, 2, destSurface);
                        // Non-gradient: nothing
                        drawGradient(xOffset + titlebarButtonX + pos * titlebarButtonSize + 2, yOffset + titlebarButtonY + 2, titlebarButtonSize - 4, titlebarButtonSize - 4, guiRGB[COLOR_BUTTONFILL1], guiRGB[COLOR_BUTTONFILL2], destSurface);
                        drawText(text, guiRGB[COLOR_TEXT], xOffset + titlebarButtonX + pos * titlebarButtonSize + titlebarButtonXPad, yOffset + titlebarButtonY + titlebarButtonXPad, destSurface, titlebarType == TITLEBAR_TOOL ? FONT_TOOLTITLEBARWIDGET : FONT_TITLEBARWIDGET);
                    }
                }
    
                // (clip text further- done last due to new clip rectangle)
                Rect titleTextClip = { xOffset + titlebarX + 1, yOffset + titlebarY + 1, titlebarWidth - 2 - titlebarButtonTotalWidth, titlebarHeight - 2 };
                intersectRects(titleTextClip, titleClip);
                SDL_SetClipRect(destSurface, &titleTextClip);
                drawText(titlebar, guiRGB[COLOR_TEXT], xOffset + titlebarX + (titlebarType == TITLEBAR_TOOL ? FRAME_TOOLTITLELEFTPAD : FRAME_TITLELEFTPAD), yOffset + titlebarY + (titlebarType == TITLEBAR_TOOL ? FRAME_TOOLTITLETOPPAD : FRAME_TITLETOPPAD), destSurface, titlebarType == TITLEBAR_TOOL ? FONT_TOOLTITLEBAR : FONT_TITLEBAR);
            }
        }
        
        // (make sure vertical exists before doing more work)
        if (vScrollbarX) {
            // Determine clip area for vertical scrollbar
            Rect vertClip = { vScrollbarX + xOffset, vScrollbarY + yOffset, vScrollbarWidth, vScrollbarHeight };
            intersectRects(vertClip, clipArea);
        
            // If we need to update vertical scroll, OR it intersects with the dirty area,
            // redraw it; note that if the second conditional is tested, vertClip will be reduced
            // to whatever portion intersects with the dirty area, which is what we want
            if ((whyDirty & FRAME_DIRTY_SCROLLVERT) || (intersectRects(vertClip, toDisplay))) {
                // Add vertical rect into updated area
                boundRects(updatedArea, vertClip);
            
                // Draw vertical scrollbar
                SDL_SetClipRect(destSurface, &vertClip);
                SDL_FillRect(destSurface, &vertClip, guiPacked[COLOR_SCROLLTRACK]);
                if (vScrollbarTabSize) {
                    // Invert if pressed
                    if ((dragMode == FRAME_DRAG_SCROLLBAR_VERT) && (dragItem == FRAME_SCROLL_TAB)) {
                        drawRect(xOffset + vScrollbarX, yOffset + vScrollbarY + scrollbarButtonSize + vScrollbarTab, scrollbarButtonSize, vScrollbarTabSize, guiPacked[COLOR_FILL], destSurface);
                        drawGuiBoxInvert(xOffset + vScrollbarX, yOffset + vScrollbarY + scrollbarButtonSize + vScrollbarTab, scrollbarButtonSize, vScrollbarTabSize, 2, destSurface);
                    }
                    else {
                        drawGuiBox(xOffset + vScrollbarX, yOffset + vScrollbarY + scrollbarButtonSize + vScrollbarTab, scrollbarButtonSize, vScrollbarTabSize, 2, destSurface);
                    }
                }
                // if pressed
                if ((dragMode == FRAME_DRAG_SCROLLBAR_VERT) && (dragItem == FRAME_SCROLL_LESS)) {
                    // Non-gradient: drawRect(xOffset + vScrollbarX, yOffset + vScrollbarY, scrollbarButtonSize, scrollbarButtonSize, guiPacked[COLOR_FILL], destSurface);
                    drawGradient(xOffset + vScrollbarX, yOffset + vScrollbarY, scrollbarButtonSize, scrollbarButtonSize, guiRGB[COLOR_BUTTONDOWN1], guiRGB[COLOR_BUTTONDOWN2], destSurface);
                    drawGuiBoxInvert(xOffset + vScrollbarX, yOffset + vScrollbarY, scrollbarButtonSize, scrollbarButtonSize, 2, destSurface);
                    drawText(wtButtonUp, guiRGB[COLOR_TEXT], xOffset + vScrollbarX + scrollbarButtonXPad + 1, yOffset + vScrollbarY + scrollbarButtonXPad + 1, destSurface, FONT_WIDGET);
                }
                else {
                    drawGuiBox(xOffset + vScrollbarX, yOffset + vScrollbarY, scrollbarButtonSize, scrollbarButtonSize, 2, destSurface);
                    // Non-gradient: nothing
                    drawGradient(xOffset + vScrollbarX + 2, yOffset + vScrollbarY + 2, scrollbarButtonSize - 4, scrollbarButtonSize - 4, guiRGB[COLOR_BUTTONFILL1], guiRGB[COLOR_BUTTONFILL2], destSurface);
                    drawText(wtButtonUp, guiRGB[COLOR_TEXT], xOffset + vScrollbarX + scrollbarButtonXPad, yOffset + vScrollbarY + scrollbarButtonXPad, destSurface, FONT_WIDGET);
                }
                // if pressed
                if ((dragMode == FRAME_DRAG_SCROLLBAR_VERT) && (dragItem == FRAME_SCROLL_MORE)) {
                    // Non-gradient: drawRect(xOffset + vScrollbarX, yOffset + vScrollbarY + vScrollbarHeight - scrollbarButtonSize, scrollbarButtonSize, scrollbarButtonSize, guiPacked[COLOR_FILL], destSurface);
                    drawGradient(xOffset + vScrollbarX, yOffset + vScrollbarY + vScrollbarHeight - scrollbarButtonSize, scrollbarButtonSize, scrollbarButtonSize, guiRGB[COLOR_BUTTONDOWN1], guiRGB[COLOR_BUTTONDOWN2], destSurface);
                    drawGuiBoxInvert(xOffset + vScrollbarX, yOffset + vScrollbarY + vScrollbarHeight - scrollbarButtonSize, scrollbarButtonSize, scrollbarButtonSize, 2, destSurface);
                    drawText(wtButtonDown, guiRGB[COLOR_TEXT], xOffset + vScrollbarX + scrollbarButtonXPad + 1, yOffset + vScrollbarY + vScrollbarHeight - scrollbarButtonSize + scrollbarButtonXPad + 1, destSurface, FONT_WIDGET);
                }
                else {
                    drawGuiBox(xOffset + vScrollbarX, yOffset + vScrollbarY + vScrollbarHeight - scrollbarButtonSize, scrollbarButtonSize, scrollbarButtonSize, 2, destSurface);
                    // Non-gradient: nothing
                    drawGradient(xOffset + vScrollbarX + 2, yOffset + vScrollbarY + vScrollbarHeight - scrollbarButtonSize + 2, scrollbarButtonSize - 4, scrollbarButtonSize - 4, guiRGB[COLOR_BUTTONFILL1], guiRGB[COLOR_BUTTONFILL2], destSurface);
                    drawText(wtButtonDown, guiRGB[COLOR_TEXT], xOffset + vScrollbarX + scrollbarButtonXPad, yOffset + vScrollbarY + vScrollbarHeight - scrollbarButtonSize + scrollbarButtonXPad, destSurface, FONT_WIDGET);
                }
            }
        }

        // (make sure horizontal exists before doing more work)
        if (hScrollbarY) {
            // Determine clip area for horizontal scrollbar
            Rect horizClip = { hScrollbarX + xOffset, hScrollbarY + yOffset, hScrollbarWidth, hScrollbarHeight };
            intersectRects(horizClip, clipArea);
        
            // If we need to update horizontal scroll, OR it intersects with the dirty area,
            // redraw it; note that if the second conditional is tested, vertClip will be reduced
            // to whatever portion intersects with the dirty area, which is what we want
            if ((whyDirty & FRAME_DIRTY_SCROLLHORIZ) || (intersectRects(horizClip, toDisplay))) {
                // Add horizontal rect into updated area
                boundRects(updatedArea, horizClip);
            
                // Draw horizontal scrollbar
                SDL_SetClipRect(destSurface, &horizClip);
                SDL_FillRect(destSurface, &horizClip, guiPacked[COLOR_SCROLLTRACK]);
                if (hScrollbarTabSize) {
                    // Invert if pressed
                    if ((dragMode == FRAME_DRAG_SCROLLBAR_VERT) && (dragItem == FRAME_SCROLL_TAB)) {
                        drawRect(xOffset + hScrollbarX + scrollbarButtonSize + hScrollbarTab, yOffset + hScrollbarY, hScrollbarTabSize, scrollbarButtonSize, guiPacked[COLOR_FILL], destSurface);
                        drawGuiBoxInvert(xOffset + hScrollbarX + scrollbarButtonSize + hScrollbarTab, yOffset + hScrollbarY, hScrollbarTabSize, scrollbarButtonSize, 2, destSurface);
                    }
                    else {
                        drawGuiBox(xOffset + hScrollbarX + scrollbarButtonSize + hScrollbarTab, yOffset + hScrollbarY, hScrollbarTabSize, scrollbarButtonSize, 2, destSurface);
                    }
                }
                // if pressed
                if ((dragMode == FRAME_DRAG_SCROLLBAR_HORIZ) && (dragItem == FRAME_SCROLL_LESS)) {
                    // Non-gradient: drawRect(xOffset + hScrollbarX, yOffset + hScrollbarY, scrollbarButtonSize, scrollbarButtonSize, guiPacked[COLOR_FILL], destSurface);
                    drawGradient(xOffset + hScrollbarX, yOffset + hScrollbarY, scrollbarButtonSize, scrollbarButtonSize, guiRGB[COLOR_BUTTONDOWN1], guiRGB[COLOR_BUTTONDOWN2], destSurface);
                    drawGuiBoxInvert(xOffset + hScrollbarX, yOffset + hScrollbarY, scrollbarButtonSize, scrollbarButtonSize, 2, destSurface);
                    drawText(wtButtonLeft, guiRGB[COLOR_TEXT], xOffset + hScrollbarX + scrollbarButtonXPad + 1, yOffset + hScrollbarY + scrollbarButtonXPad + 1, destSurface, FONT_WIDGET);
                }
                else {
                    drawGuiBox(xOffset + hScrollbarX, yOffset + hScrollbarY, scrollbarButtonSize, scrollbarButtonSize, 2, destSurface);
                    // Non-gradient: nothing
                    drawGradient(xOffset + hScrollbarX + 2, yOffset + hScrollbarY + 2, scrollbarButtonSize - 4, scrollbarButtonSize - 4, guiRGB[COLOR_BUTTONFILL1], guiRGB[COLOR_BUTTONFILL2], destSurface);
                    drawText(wtButtonLeft, guiRGB[COLOR_TEXT], xOffset + hScrollbarX + scrollbarButtonXPad, yOffset + hScrollbarY + scrollbarButtonXPad, destSurface, FONT_WIDGET);
                }
                // if pressed
                if ((dragMode == FRAME_DRAG_SCROLLBAR_HORIZ) && (dragItem == FRAME_SCROLL_MORE)) {
                    // Non-gradient: drawRect(xOffset + hScrollbarX + hScrollbarWidth - scrollbarButtonSize, yOffset + hScrollbarY, scrollbarButtonSize, scrollbarButtonSize, guiPacked[COLOR_FILL], destSurface);
                    drawGradient(xOffset + hScrollbarX + hScrollbarWidth - scrollbarButtonSize, yOffset + hScrollbarY, scrollbarButtonSize, scrollbarButtonSize, guiRGB[COLOR_BUTTONDOWN1], guiRGB[COLOR_BUTTONDOWN2], destSurface);
                    drawGuiBoxInvert(xOffset + hScrollbarX + hScrollbarWidth - scrollbarButtonSize, yOffset + hScrollbarY, scrollbarButtonSize, scrollbarButtonSize, 2, destSurface);
                    drawText(wtButtonRight, guiRGB[COLOR_TEXT], xOffset + hScrollbarX + hScrollbarWidth - scrollbarButtonSize + scrollbarButtonXPad + 1, yOffset + hScrollbarY + scrollbarButtonXPad + 1, destSurface, FONT_WIDGET);
                }
                else {
                    drawGuiBox(xOffset + hScrollbarX + hScrollbarWidth - scrollbarButtonSize, yOffset + hScrollbarY, scrollbarButtonSize, scrollbarButtonSize, 2, destSurface);
                    // Non-gradient: nothing
                    drawGradient(xOffset + hScrollbarX + hScrollbarWidth - scrollbarButtonSize + 2, yOffset + hScrollbarY + 2, scrollbarButtonSize - 4, scrollbarButtonSize - 4, guiRGB[COLOR_BUTTONFILL1], guiRGB[COLOR_BUTTONFILL2], destSurface);
                    drawText(wtButtonRight, guiRGB[COLOR_TEXT], xOffset + hScrollbarX + hScrollbarWidth - scrollbarButtonSize + scrollbarButtonXPad, yOffset + hScrollbarY + scrollbarButtonXPad, destSurface, FONT_WIDGET);
                }
            }
        }

        // Fill in corner?
        if ((vScrollbarX) && (hScrollbarY)) {
            Rect cornerClip = { vScrollbarX + xOffset, hScrollbarY + yOffset, vScrollbarWidth, hScrollbarHeight };
            intersectRects(cornerClip, clipArea);

            // Only needed if area is dirty
            if (intersectRects(cornerClip, toDisplay)) {
                // We set clip because it may have been reset earlier
                SDL_SetClipRect(destSurface, &cornerClip);
                SDL_FillRect(destSurface, &cornerClip, guiPacked[COLOR_FILL]);
            }
        }

        // Panel areas?
        int max = clientArea.size();
        for (int pos = 1; pos < max; ++pos) {
            if ((!(clientArea[pos].position & CLIENT_NOTMANAGED)) && (clientArea[pos].window)) {
                // Panel area that CAN be drawn
                Rect panelClip = { clientArea[pos].x + xOffset, clientArea[pos].y + yOffset, clientArea[pos].widthShown, clientArea[pos].heightShown };
                intersectRects(panelClip, clipArea);

                // Panel area that MUST be drawn
                Rect panelDirty = panelClip;

                // If we need to update panel, OR it intersects with the dirty area, redraw it
                // We test dirty first, because we always want the dirty area to be what
                // the panel HAS to update, even if they're dirty...
                if ((intersectRects(panelDirty, toDisplay)) || (clientArea[pos].window->isDirty())) {
                    // ... but we clip to a wider area- the area the panel CAN update
                    SDL_SetClipRect(destSurface, &panelClip);

                    // Background fill on dirty area
                    SDL_FillRect(destSurface, &panelDirty, guiPacked[COLOR_FILL]);

                    // Paint panel area
                    clientArea[pos].window->display(destSurface, panelDirty, panelClip, xOffset, yOffset);
                    // Add panel rect into updated area
                    boundRects(updatedArea, panelDirty);
                }
            }
        }

        // Client area exists? (done separate due to 'inner' setting)
        if ((clientWidthInner > 0) && (clientHeightInner > 0)) {
            // Client area that CAN be drawn
            Rect clientClip = { clientArea[0].x + xOffset, clientArea[0].y + yOffset, clientWidthInner, clientHeightInner };
            intersectRects(clientClip, clipArea);
            
            // Client area that MUST be drawn
            Rect clientDirty = clientClip;
        
            // If we need to update client, OR it intersects with the dirty area, redraw it
            int clientIsDirty = 0;
            if ((clientArea[0].window) && (clientArea[0].window->isDirty())) clientIsDirty = 1;
            // We test dirty first, because we always want the dirty area to be what
            // the client HAS to update, even if they're dirty...
            if ((intersectRects(clientDirty, toDisplay)) || (clientIsDirty)) {
                // ... but we clip to a wider area- the area the client CAN update
                SDL_SetClipRect(destSurface, &clientClip);
                
                // Background fill on dirty area
                SDL_FillRect(destSurface, &clientDirty, guiPacked[frameType == FRAMETYPE_BEVEL_BK ? COLOR_BKFILL : frameType == FRAMETYPE_BEVEL_TEXT ? COLOR_TEXTBOX : COLOR_FILL]);

                // Paint client area
                if (clientArea[0].window) {
                    clientArea[0].window->display(destSurface, clientDirty, clientClip, xOffset, yOffset);
                    // Add client rect into updated area
                    boundRects(updatedArea, clientDirty);
                }
            }
        }
        
        // Float target?
        if (floatTarget) {
            SDL_SetClipRect(destSurface, &toDisplay);
            drawSelectRect(xOffset + floatTargetX, yOffset + floatTargetY, floatTargetWidth, floatTargetHeight, guiPacked[COLOR_SELECTION1], destSurface);
        }
        
        toDisplay = updatedArea;
    }

    dirty = 0;
    childDirty = 0;
    whyDirty = FRAME_DIRTY_NONE;
}

/* Not currently used or supported
int FrameWindow::usesAlpha() { start_func
    int uses = 0;
    for (int cl = 0; cl <= 2; ++cl) {
        if (clientArea[cl].window) uses = uses || clientArea[cl].window->usesAlpha();
    }
    return uses;
}
*/

Window::WindowSort FrameWindow::windowSort() const { start_func
    if (clientArea[0].window != NULL) return clientArea[0].window->windowSort();
    return WINDOWSORT_NORMAL;
}

Window::CommandSupport FrameWindow::supportsCommand(int code) const { start_func
    // Frame window commands
    if (code == WINDOW_MIN) return ((state == FRAME_STATE_MINIMIZED) || strchr(titlebarButtons, 'h')) ? Window::COMMAND_ENABLE : Window::COMMAND_DISABLE;
    if (code == WINDOW_MAX) return ((state == FRAME_STATE_MAXIMIZED) || strchr(titlebarButtons, 'g')) ? Window::COMMAND_ENABLE : Window::COMMAND_DISABLE;
    if (code == WINDOW_SWITCHPANEL) {
        int max = clientArea.size();
        for (int pos = 1; pos < max; ++pos) {
            if (clientArea[pos].window) {
                // @TODO: should this also work on floating/desktop panels?
                if (!(clientArea[pos].position & CLIENT_NOTMANAGED)) return Window::COMMAND_ENABLE;
            }
        }
        return Window::COMMAND_HIDE;
    }
    // @TODO: VIEW_TOOLPANEL/VIEW_* to toggle panel visibility
    if ((code == WINDOW_CLOSE) || (code == WINDOW_NEXT) || (code == WINDOW_PREV) ||
        (code == WINDOW_CLOSEALL) || (code == WINDOW_CLOSEALLBUT) || (code == WINDOW_TILEHORIZ) ||
        (code == WINDOW_TILEVERT) || (code == WINDOW_CASCADE)) return Window::COMMAND_ENABLE;

    CommandSupport uses = Window::COMMAND_HIDE;
    vector<ClientArea>::const_iterator end = clientArea.end();
    for (vector<ClientArea>::const_iterator pos = clientArea.begin(); pos != clientArea.end(); ++pos) {
        if ((*pos).window) {
            if (uses == Window::COMMAND_HIDE) uses = (*pos).window->supportsCommand(code);
            else {
                CommandSupport sc = (*pos).window->supportsCommand(code);
                if (sc & Window::COMMAND_ENABLE) return sc;
            }
        }
    }
    return uses;
}

int FrameWindow::wantsToBeDeleted() const { start_func
    return deleteMe;
}

void FrameWindow::setWantsToBeDeleted(int fDeleteMe) { start_func
    deleteMe = fDeleteMe;
}

int FrameWindow::attemptClose() { start_func
    if (clientArea[0].window != NULL) {
        return clientArea[0].window->attemptClose();
    }

    return 1;
}

void FrameWindow::scrollToView(int sX, int sY, int sWidth, int sHeight) { start_func
    int cX = -scrollX;
    int cY = -scrollY;

    // Don't adjust left-right if target encompasses entire view
    if ((sX > cX) || (sX + sWidth < cX + clientWidthInner)) {
        // If right-most edge of target is to the right of us, scroll to see it
        if (sX + sWidth > cX + clientWidthInner) cX = sX + sWidth - clientWidthInner;
    
        // Same for left
        if (sX < cX) cX = sX;
    }

    // Don't adjust up-down if target encompasses entire view
    if ((sY > cY) || (sY + sHeight < cY + clientHeightInner)) {
        // Same for bottom, top
        if (sY + sHeight > cY + clientHeightInner) cY = sY + sHeight - clientHeightInner;
        if (sY < cY) cY = sY;
    }
    
    // Complete scroll
    scrollTo(-cX, -cY);
}

void FrameWindow::scrollBy(int sX, int sY) { start_func
    scrollTo(scrollX + sX, scrollY + sY);
}

void FrameWindow::scrollTo(int sX, int sY, int tabX, int tabY) { start_func
    if (clientArea[0].window) {
        // Not too far in either direction
        if (sX < min(0, clientWidthInner - clientActualWidth)) sX = min(0, clientWidthInner - clientActualWidth);
        if (sY < min(0, clientHeightInner - clientActualHeight)) sY = min(0, clientHeightInner - clientActualHeight);

        // Check this direction last in case window is too large overall
        // Only allow scrolling "positive" if autocenter enabled
        if (autoCenterScroll) {
            if (sX > max(0, clientWidthInner - clientActualWidth)) sX = max(0, clientWidthInner - clientActualWidth);
            if (sY > max(0, clientHeightInner - clientActualHeight)) sY = max(0, clientHeightInner - clientActualHeight);
        }
        else {
            if (sX > 0) sX = 0;
            if (sY > 0) sY = 0;
        }
        
        // Tab positions
        if ((vScrollbarTabSize) && (clientHeightInner < clientActualHeight)) {
            int newTab;
            if (tabY >= 0) newTab = tabY;
            else newTab = (vScrollbarHeight - (scrollbarButtonSize * 2) - vScrollbarTabSize) * sY / (clientHeightInner - clientActualHeight);
            if (newTab != vScrollbarTab) {
                setDirty();
                whyDirty |= FRAME_DIRTY_SCROLLVERT;
            }
            vScrollbarTab = newTab;
        }
        else {
            if (vScrollbarTab != 0) {
                setDirty();
                whyDirty |= FRAME_DIRTY_SCROLLVERT;
            }
            vScrollbarTab = 0;
        }
        if ((hScrollbarTabSize) && (clientWidthInner < clientActualWidth)) {
            int newTab;
            if (tabX >= 0) newTab = tabX;
            else newTab = (hScrollbarWidth - (scrollbarButtonSize * 2) - hScrollbarTabSize) * sX / (clientWidthInner - clientActualWidth);
            if (newTab != hScrollbarTab) {
                setDirty();
                whyDirty |= FRAME_DIRTY_SCROLLHORIZ;
            }
            hScrollbarTab = newTab;
        }
        else {
            if (hScrollbarTab != 0) {
                setDirty();
                whyDirty |= FRAME_DIRTY_SCROLLHORIZ;
            }
            hScrollbarTab = 0;
        }
        
        if ((scrollX != sX) || (scrollY != sY)) {
            scrollX = sX;
            scrollY = sY;
            clientArea[0].window->move(clientArea[0].x + scrollX, clientArea[0].y + scrollY);
        }
    }
    else {
        // No client, no scrollbars- reset to zero- dirty if wasn't already at zero
        if ((scrollX) || (scrollY)) {
            setDirty();
            whyDirty = FRAME_DIRTY_ALL;
        }
        scrollX = scrollY = 0;
    }
}

void FrameWindow::confineSize(int& sX, int& sY, int& sWidth, int& sHeight) const { start_func
    int clipX, clipY, clipW, clipH;
    
    // Size is not confined by us if we aren't on desktop
    if ((parent != desktop) && (parent != NULL)) return;

    if (windowSort() >= WINDOWSORT_MODAL) {
        clipX = 0;
        clipY = 0;
        clipW = screenWidth;
        clipH = screenHeight;
    }
    else {
        clipX = desktop->desktopX();
        clipY = desktop->desktopY();
        clipW = desktop->desktopWidth();
        clipH = desktop->desktopHeight();
    }

    // Ensure fits on-screen
    if (sX + sWidth > clipX + clipW) {
        sX = clipX + clipW - sWidth;
    }
    if (sX < clipX) {
        sX = clipX;
        if (sWidth > clipW) sWidth = clipW;
    }
    if (sY + sHeight > clipY + clipH) {
        sY = clipY + clipH - sHeight;
    }
    if (sY < clipY) {
        sY = clipY;
        if (sHeight > clipH) sHeight = clipH;
    }
    
    // Ensure at least minimum size
    if (sHeight < (titlebarHeight + 2 * outerBorder)) {
        sHeight = (titlebarHeight + 2 * outerBorder);
    }
    if (sWidth < (outerBorder * 2 + 2 + titlebarButtonTotalWidth)) {
        sWidth = (outerBorder * 2 + 2 + titlebarButtonTotalWidth);
    }
}

void FrameWindow::show(int xPos, int yPos, int fWidth, int fHeight) { start_func
    assert(xPos >= SHOW_CURRMIN);
    assert(yPos >= SHOW_CURRMIN);
    assert(fWidth >= SHOW_CURRMIN);
    assert(fHeight >= SHOW_CURRMIN);

    int cascadeOffset = outerBorder + titlebarHeight;

    if (fWidth == SHOW_CASCADE) fWidth = desktop->desktopWidth() - cascadeOffset * 3;
    if (fHeight == SHOW_CASCADE) fHeight = desktop->desktopHeight() - cascadeOffset * 3;
    if (fWidth == SHOW_CURRENT) fWidth = width;
    if (fHeight == SHOW_CURRENT) fHeight = height;
    if (fWidth == SHOW_CURRMIN) {
        fWidth = width;
        if (fWidth < DEFAULT_MIN_WIDTH) fWidth = DEFAULT_MIN_WIDTH;
    }
    if (fHeight == SHOW_CURRMIN) {
        fHeight = height;
        if (fHeight < DEFAULT_MIN_HEIGHT) fHeight = DEFAULT_MIN_HEIGHT;
    }

    if ((xPos == SHOW_CASCADE) || (yPos == SHOW_CASCADE)) {
        if (++cascadeStep > 3) cascadeStep = 0;
    }
    if (xPos == SHOW_CASCADE) {
        xPos = desktop->desktopX() + cascadeStep * cascadeOffset;
    }
    if (yPos == SHOW_CASCADE) {
        yPos = desktop->desktopY() + cascadeStep * cascadeOffset;
    }
    if ((xPos == SHOW_CURRENT) || (xPos == SHOW_CURRMIN)) xPos = x;
    if ((yPos == SHOW_CURRENT) || (yPos == SHOW_CURRMIN)) yPos = y;

    confineSize(xPos, yPos, fWidth, fHeight);

    // Move
    resize(fWidth, fHeight);
    move(xPos, yPos);
    desktop->addWindow(this, 1);
}

void FrameWindow::hide() { start_func
    // If we aren't part of desktop, this is harmless
    desktop->removeWindow(this);
}

void FrameWindow::undoNotify(int undoType, int undoItemId, int undoItemSubId, int uX, int uY, int uW, int uH) { start_func
    if (clientArea[0].window) clientArea[0].window->undoNotify(undoType, undoItemId, undoItemSubId, uX, uY, uW, uH);
}

const char* FrameWindow::tooltip(int xPos, int yPos) const { start_func
    DragType toolType;
    DragSubType toolItem;
    whereCoords(xPos, yPos, &toolType, &toolItem);
    
    if (toolType == FRAME_DRAG_CLIENT) {
        xPos -= clientArea[toolItem].x;
        yPos -= clientArea[toolItem].y;
        if (toolItem == 0) {
            xPos += scrollX;
            yPos += scrollY;
        }
        return clientArea[toolItem].window->tooltip(xPos, yPos);
    }
    
    if (toolType == FRAME_DRAG_BUTTONS) {
        switch (titlebarButtons[toolItem - 1]) {
            case 'h':
                return "Roll-up window (minimize)";
                
            case 'g':
                return "Maximize window";
                
            case 'f':
                return "Close window";
                
            case 'j':
                return "Restore window";
        }
    }

    return NULL;
}

void FrameWindow::whereCoords(int x, int y, DragType* dragType, DragSubType* dragItem) const { start_func
    assert(dragType);
    assert(dragItem);

    *dragType = FRAME_DRAG_NONE;
    *dragItem = FRAME_RESIZE_NONE;

    // Main client area? Client *can* receive clicks outside it's actual area
    if ((clientArea[0].window) &&
        (x >= clientArea[0].x) &&
        (y >= clientArea[0].y) &&
        (x < clientArea[0].x + clientWidthInner) &&
        (y < clientArea[0].y + clientHeightInner)) {
        *dragType = FRAME_DRAG_CLIENT;
        *dragItem = (DragSubType)0;
    }
    // Buttons?
    // (check before titlebar as space is shared)
    else if ((titlebarType != TITLEBAR_OFF) &&
        (x >= titlebarButtonX) &&
        (y >= titlebarButtonY) &&
        (x < titlebarButtonX + titlebarButtonTotalWidth) &&
        (y < titlebarButtonY + titlebarButtonSize)) {
        *dragType = FRAME_DRAG_BUTTONS;
        // (add 1 because 0 is a 'none' value)
        *dragItem = (DragSubType)((x - titlebarButtonX) / titlebarButtonSize + 1);
    }
    // Titlebar?
    else if ((titlebarType != TITLEBAR_OFF) &&
        (x >= titlebarX) &&
        (y >= titlebarY) &&
        (x < titlebarX + titlebarWidth) &&
        (y < titlebarY + titlebarHeight)) {
        *dragType = FRAME_DRAG_TITLE;
    }
    // Vertical scrollbar?
    else if ((x >= vScrollbarX) &&
        (x < vScrollbarX + vScrollbarWidth) &&
        (y >= vScrollbarY) &&
        (y < vScrollbarY + vScrollbarHeight)) {
        *dragType = FRAME_DRAG_SCROLLBAR_VERT;
        
        if (y < vScrollbarY + scrollbarButtonSize) *dragItem = FRAME_SCROLL_LESS;
        else if (y >= vScrollbarY + vScrollbarHeight - scrollbarButtonSize) *dragItem = FRAME_SCROLL_MORE;
        else if (y < vScrollbarY + scrollbarButtonSize + vScrollbarTab) *dragItem = FRAME_SCROLL_PAGELESS;
        else if (y >= vScrollbarY + scrollbarButtonSize + vScrollbarTab + vScrollbarTabSize) *dragItem = FRAME_SCROLL_PAGEMORE;
        else *dragItem = FRAME_SCROLL_TAB;
    }
    // Horizontal scrollbar?
    else if ((x >= hScrollbarX) &&
        (x < hScrollbarX + hScrollbarWidth) &&
        (y >= hScrollbarY) &&
        (y < hScrollbarY + hScrollbarHeight)) {
        *dragType = FRAME_DRAG_SCROLLBAR_HORIZ;

        if (x < hScrollbarX + scrollbarButtonSize) *dragItem = FRAME_SCROLL_LESS;
        else if (x >= hScrollbarX + hScrollbarWidth - scrollbarButtonSize) *dragItem = FRAME_SCROLL_MORE;
        else if (x < hScrollbarX + scrollbarButtonSize + hScrollbarTab) *dragItem = FRAME_SCROLL_PAGELESS;
        else if (x >= hScrollbarX + scrollbarButtonSize + hScrollbarTab + hScrollbarTabSize) *dragItem = FRAME_SCROLL_PAGEMORE;
        else *dragItem = FRAME_SCROLL_TAB;
    }
    else {
        // Tool panels?
        int max = clientArea.size();
        for (int pos = 1; pos < max; ++pos) {
            if (clientArea[pos].window) {
                if (!(clientArea[pos].position & CLIENT_NOTMANAGED)) {
                    if ((x >= clientArea[pos].x) &&
                        (y >= clientArea[pos].y) &&
                        (x < clientArea[pos].x + clientArea[pos].widthShown) &&
                        (y < clientArea[pos].y + clientArea[pos].heightShown) &&
                        (x < clientArea[pos].x + clientArea[pos].window->getWidth()) &&
                        (y < clientArea[pos].y + clientArea[pos].window->getHeight())) {
                        *dragType = FRAME_DRAG_CLIENT;
                        *dragItem = (DragSubType)pos;
                        return;
                    }
                }
            }
        }
    
        // Resize?
        // (no resizing when maximized, minimized, no border, or prevented)
        if (state != FRAME_STATE_NORMAL) return;
        if ((!resizable) || (!outerBorder)) return;

        DragSubType resizing = FRAME_RESIZE_NONE;
        int sensitive = outerBorder + FRAME_INNERBORDER;

        if ((y >= 0) && (y < sensitive)) {
            resizing = (DragSubType)(resizing | FRAME_RESIZE_TOP);
            sensitive = RESIZE_CORNER_SENSITIVITY;
        }
        if ((y >= height - sensitive) && (y < height)) {
            resizing = (DragSubType)(resizing | FRAME_RESIZE_BOTTOM);
            sensitive = RESIZE_CORNER_SENSITIVITY;
        }
        if ((x >= 0) && (x < sensitive)) {
            resizing = (DragSubType)(resizing | FRAME_RESIZE_LEFT);
            sensitive = RESIZE_CORNER_SENSITIVITY;
        }
        if ((x >= width - sensitive) && (x < width)) {
            resizing = (DragSubType)(resizing | FRAME_RESIZE_RIGHT);
            sensitive = RESIZE_CORNER_SENSITIVITY;
        }
        // Redo the Ys due to possibile sensitivity change from finding an X match
        if ((y >= 0) && (y < sensitive))
            resizing = (DragSubType)(resizing | FRAME_RESIZE_TOP);
        if ((y >= height - sensitive) && (y < height))
            resizing = (DragSubType)(resizing | FRAME_RESIZE_BOTTOM);

        if (resizing) {
            *dragType = FRAME_DRAG_BORDER;
            *dragItem = resizing;
        }
    }
}

int FrameWindow::event(int hasFocus, const SDL_Event* event) { start_func
    assert(event);

    DragType newDragMode;
    DragSubType newDragItem;
    int pos;
    int used = 0;
    Uint32 ticks;
    int mX, mY;
    int done = 0;
    Window* exempt = NULL;
    
    // We need to copy the event if we expect to change it to send a copy to client
    SDL_Event clientEvent = *event;

    switch (event->type) {
        case SDL_CLOSE:
            // Close client(s), then us
            for (pos = 0; pos < (int)clientArea.size(); ++pos) {
                if (clientArea[pos].window) {
                    // Shouldn't delete itself here
                    clientArea[pos].window->event(hasFocus && (subFocus == pos), &clientEvent);
                    // Prevent it from notifying us when it DOES delete itself ("we know")
                    clientArea[pos].window->setParent(NULL);
                    clientArea[pos].window->setParentNotify(NULL);
                    if (clientArea[pos].window->wantsToBeDeleted()) delete clientArea[pos].window;
                    clientArea[pos].window = NULL;
                }
            }
            
            if (parent == desktop) {
                desktop->removeWindow(this);

                // Make sure next frame window is selected
                pos = 0;
                while (desktop->findWindow(WINDOW_FRAME, pos) != NULL) {
                    ++pos;
                }

                if (--pos >= 0) {
                    desktop->bringToTop(desktop->findWindow(WINDOW_FRAME, pos));
                }
            }

            return 1;
            
        case SDL_MOUSEBUTTONUP:
            if (dragMode == FRAME_DRAG_CLIENT) {
                // Adjust coordinates to 0-based for client
                if (clientArea[dragItem].window) {
                    clientEvent.button.x -= clientArea[dragItem].x + (dragItem == 0 ? scrollX : 0);
                    clientEvent.button.y -= clientArea[dragItem].y + (dragItem == 0 ? scrollY : 0);

                    // Propogate to client
                    clientArea[dragItem].window->event(hasFocus && (subFocus == dragItem), &clientEvent);
                }
                
                if ((SDL_GetMouseState(NULL, NULL) & (SDL_BUTTON_LMASK | SDL_BUTTON_RMASK)) == 0) done = 1;
            }
            else if (event->button.button == SDL_BUTTON_LEFT) {
                // Button pressed?
                if ((dragMode == FRAME_DRAG_BUTTONS) && (dragItem)) {
                    switch (titlebarButtons[(int)(dragItem - 1)]) {
                        case 'h':
                            minimize();
                            break;
                            
                        case 'g':
                            maximize();
                            break;
                            
                        case 'f':
                            closeWindow(); // Calls attemptClose
                            break;
                            
                        case 'j':
                            restore();
                            break;
                    }
                    setDirty();
                    whyDirty |= FRAME_DIRTY_TITLE;
                }
                else if (dragMode == FRAME_DRAG_SCROLLBAR_HORIZ) {
                    setDirty();
                    whyDirty |= FRAME_DIRTY_SCROLLHORIZ;
                }
                else if (dragMode == FRAME_DRAG_SCROLLBAR_VERT) {
                    setDirty();
                    whyDirty |= FRAME_DIRTY_SCROLLVERT;
                }
                else if (dragMode == FRAME_DRAG_TITLE) {
                    if ((framePanel) && (parent == desktop)) {
                        int index = framePanel->findToolPanel(this);
                        if (index) framePanel->dragFloatPanel(index, 1);
                    }
                }
                
                done = 1;
            }
            
            if (done) {
                scrollRepeatTicks = scrollRepeatDelay = 0;
                dragMode = FRAME_DRAG_NONE;
                doMouseHover(event->motion.x, event->motion.y, hasFocus, clientEvent);
            }
            break;
        
        case SDL_MOUSEBUTTONDBL:
        case SDL_MOUSEBUTTONDOWN:
            // Dragging/clicking what?
            whereCoords(event->button.x, event->button.y, &newDragMode, &newDragItem);
            
            if (newDragMode == FRAME_DRAG_CLIENT) {
                // No focus or drag on wheels
                if ((event->button.button != SDL_BUTTON_WHEELUP) &&
                    (event->button.button != SDL_BUTTON_WHEELDOWN)) {
                    dragMode = newDragMode;
                    dragItem = newDragItem;
                    focusToolPanel(dragItem);
                }

                // Adjust coordinates to 0-based for client
                if (clientArea[newDragItem].window) {
                    clientEvent.button.x -= clientArea[newDragItem].x + (newDragItem == 0 ? scrollX : 0);
                    clientEvent.button.y -= clientArea[newDragItem].y + (newDragItem == 0 ? scrollY : 0);

                    // Propogate to client
                    if (!clientArea[newDragItem].window->event(hasFocus && (subFocus == newDragItem), &clientEvent)) {
                        // If not used, handle mouse wheel ourselves
                        if ((newDragItem == 0) && (clientActualHeight > clientHeightInner)) {
                            if (event->button.button == SDL_BUTTON_WHEELUP) {
                                scrollTo(scrollX, scrollY + vScrollPage);
                            }
                            else if (event->button.button == SDL_BUTTON_WHEELDOWN) {
                                scrollTo(scrollX, scrollY - vScrollPage);
                            }
                        }
                    }
                }
            }
            else if ((event->type == SDL_MOUSEBUTTONDBL) && (newDragMode == FRAME_DRAG_TITLE)) {
                if ((state == FRAME_STATE_MINIMIZED) || (state == FRAME_STATE_MAXIMIZED)) restore();
                else minimize();
            }
            else if (((event->button.button == SDL_BUTTON_WHEELUP) || (event->button.button == SDL_BUTTON_WHEELDOWN)) &&
                     ((newDragMode == FRAME_DRAG_SCROLLBAR_VERT) || (newDragMode == FRAME_DRAG_SCROLLBAR_HORIZ))) {
                if (newDragMode == FRAME_DRAG_SCROLLBAR_VERT) {
                    setDirty();
                    whyDirty |= FRAME_DIRTY_SCROLLVERT;
                    if (event->button.button == SDL_BUTTON_WHEELUP) scrollTo(scrollX, scrollY + vScrollPage);
                    else if (event->button.button == SDL_BUTTON_WHEELDOWN) scrollTo(scrollX, scrollY - vScrollPage);
                }
                if (newDragMode == FRAME_DRAG_SCROLLBAR_HORIZ) {
                    setDirty();
                    whyDirty |= FRAME_DIRTY_SCROLLHORIZ;
                    if (event->button.button == SDL_BUTTON_WHEELUP) scrollTo(scrollX + hScrollPage, scrollY);
                    else if (event->button.button == SDL_BUTTON_WHEELDOWN) scrollTo(scrollX - hScrollPage, scrollY);
                }
            }
            else if (event->button.button == SDL_BUTTON_LEFT) {
                if (newDragMode == FRAME_DRAG_SCROLLBAR_VERT) {
                    setDirty();
                    whyDirty |= FRAME_DIRTY_SCROLLVERT;
                    scrollRepeatTicks = SDL_GetTicks();
                    scrollRepeatDelay = DELAY_SCROLLBAR_REPEATSTART;
                    if (newDragItem == FRAME_SCROLL_LESS) scrollTo(scrollX, scrollY + vScrollLine);
                    else if (newDragItem == FRAME_SCROLL_MORE) scrollTo(scrollX, scrollY - vScrollLine);
                    else if (newDragItem == FRAME_SCROLL_PAGELESS) scrollTo(scrollX, scrollY + vScrollPage);
                    else if (newDragItem == FRAME_SCROLL_PAGEMORE) scrollTo(scrollX, scrollY - vScrollPage);
                    // Force mouse motion to determine new drag mode
                    SDL_GetMouseState(&mX, &mY);
                    SDL_WarpMouse(mX, mY);
                }
                if (newDragMode == FRAME_DRAG_SCROLLBAR_HORIZ) {
                    setDirty();
                    whyDirty |= FRAME_DIRTY_SCROLLHORIZ;
                    scrollRepeatTicks = SDL_GetTicks();
                    scrollRepeatDelay = DELAY_SCROLLBAR_REPEATSTART;
                    if (newDragItem == FRAME_SCROLL_LESS) scrollTo(scrollX + hScrollLine, scrollY);
                    else if (newDragItem == FRAME_SCROLL_MORE) scrollTo(scrollX - hScrollLine, scrollY);
                    else if (newDragItem == FRAME_SCROLL_PAGELESS) scrollTo(scrollX + hScrollPage, scrollY);
                    else if (newDragItem == FRAME_SCROLL_PAGEMORE) scrollTo(scrollX - hScrollPage, scrollY);
                    // Force mouse motion to determine new drag mode
                    SDL_GetMouseState(&mX, &mY);
                    SDL_WarpMouse(mX, mY);
                }
                if (newDragMode == FRAME_DRAG_BUTTONS) {
                    setDirty();
                    whyDirty |= FRAME_DIRTY_TITLE;
                }

                dragMode = newDragMode;
                dragItem = newDragItem;
                dragX = event->button.x;
                dragY = event->button.y;
                dragItemOverflowX = 0;
                dragItemOverflowY = 0;
            }
            break;
            
        case SDL_MOUSEFOCUS:
            // We pay attention to MOUSEMOTION; we don't propogate MOUSEFOCUS
            // gain but we must alert client to loss
            if ((hoverMode == FRAME_DRAG_CLIENT) && (clientArea[hoverItem].window)) {
                // Lose focus
                SDL_Event customEvent;
                customEvent.type = SDL_MOUSEFOCUS;
                customEvent.user.code = 0;
                customEvent.user.data1 = NULL;
                customEvent.user.data2 = NULL;
                clientArea[hoverItem].window->event(hasFocus && (subFocus == hoverItem), &customEvent);
            }
            hoverMode = FRAME_DRAG_NONE;
            break;

        case SDL_MOUSEMOTION:
            if (dragMode == FRAME_DRAG_CLIENT) {
                // Adjust coordinates to 0-based for client
                if (clientArea[dragItem].window) {
                    clientEvent.motion.x -= clientArea[dragItem].x + (dragItem == 0 ? scrollX : 0);
                    clientEvent.motion.y -= clientArea[dragItem].y + (dragItem == 0 ? scrollY : 0);

                    // Propogate to client
                    clientArea[dragItem].window->event(hasFocus && (subFocus == dragItem), &clientEvent);
                }
            }
            else if (event->motion.state & SDL_BUTTON_LMASK) {
                // These are only used if dragging within desktop
                int clipX, clipY, clipW, clipH;
                
                if (windowSort() >= WINDOWSORT_MODAL) {
                    clipX = 0;
                    clipY = 0;
                    clipW = screenWidth;
                    clipH = screenHeight;
                }
                else {
                    clipX = desktop->desktopX();
                    clipY = desktop->desktopY();
                    clipW = desktop->desktopX() + desktop->desktopWidth();
                    clipH = desktop->desktopY() + desktop->desktopHeight();
                }

                // Drag title?
                // (no moving when maximized)
                if ((dragMode == FRAME_DRAG_TITLE) && (state != FRAME_STATE_MAXIMIZED)) {
                    if (parent == desktop) {
                        int newX = x + event->motion.xrel;
                        int newY = y + event->motion.yrel;

                        // Overflow
                        if (dragItemOverflowY) {
                            newY += dragItemOverflowY;
                            dragItemOverflowY = 0;
                        }
                        if (dragItemOverflowX) {
                            newX += dragItemOverflowX;
                            dragItemOverflowX = 0;
                        }
                        if (newY + height > clipH) {
                            dragItemOverflowY = newY + height - clipH;
                            newY -= dragItemOverflowY;
                        }
                        if (newY < clipY) {
                            dragItemOverflowY = newY - clipY;
                            newY = clipY;
                        }
                        if (newX + width > clipW) {
                            dragItemOverflowX = newX + width - clipW;
                            newX -= dragItemOverflowX;
                        }
                        if (newX < clipX) {
                            dragItemOverflowX = newX - clipX;
                            newX = clipX;
                        }

                        move(newX, newY);
                        
                        if (framePanel) {
                            int index = framePanel->findToolPanel(this);
                            if (index) framePanel->dragFloatPanel(index, 0);
                        }
                    }
                    else if (framePanel) {
                        int index = framePanel->findToolPanel(this);
                        if (index) framePanel->dragToolPanel(index, event->motion.xrel, event->motion.yrel, dragItemOverflowX, dragItemOverflowY);
                    }
                }
                // Tab?
                else if ((dragMode == FRAME_DRAG_SCROLLBAR_HORIZ) &&
                         (dragItem == FRAME_SCROLL_TAB)) {
                    int newX = hScrollbarTab + event->motion.xrel;

                    // Overflow
                    if (dragItemOverflowX) {
                        newX += dragItemOverflowX;
                        dragItemOverflowX = 0;
                    }
                    if (newX + hScrollbarTabSize > (hScrollbarWidth - scrollbarButtonSize * 2)) {
                        dragItemOverflowX = newX + hScrollbarTabSize - (hScrollbarWidth - scrollbarButtonSize * 2);
                        newX -= dragItemOverflowX;
                    }
                    if (newX < 0) {
                        dragItemOverflowX = newX;
                        newX = 0;
                    }
                    
                    // Scroll to right spot
                    scrollTo(newX * (clientWidthInner - clientActualWidth) / (hScrollbarWidth - (scrollbarButtonSize * 2) - hScrollbarTabSize), scrollY, newX, -1);
                }
                else if ((dragMode == FRAME_DRAG_SCROLLBAR_VERT) &&
                         (dragItem == FRAME_SCROLL_TAB)) {
                    int newY = vScrollbarTab + event->motion.yrel;

                    // Overflow
                    if (dragItemOverflowY) {
                        newY += dragItemOverflowY;
                        dragItemOverflowY = 0;
                    }
                    if (newY + vScrollbarTabSize > (vScrollbarHeight - scrollbarButtonSize * 2)) {
                        dragItemOverflowY = newY + vScrollbarTabSize - (vScrollbarHeight - scrollbarButtonSize * 2);
                        newY -= dragItemOverflowY;
                    }
                    if (newY < 0) {
                        dragItemOverflowY = newY;
                        newY = 0;
                    }
                    
                    // Scroll to right spot
                    scrollTo(scrollX, newY * (clientHeightInner - clientActualHeight) / (vScrollbarHeight - (scrollbarButtonSize * 2) - vScrollbarTabSize), -1, newY);
                }
                // Buttons or scrollbar?
                else if ((dragMode == FRAME_DRAG_BUTTONS) ||
                         (dragMode == FRAME_DRAG_SCROLLBAR_HORIZ) ||
                         (dragMode == FRAME_DRAG_SCROLLBAR_VERT)) {
                    whereCoords(event->motion.x, event->motion.y, &newDragMode, &newDragItem);
                    if (dragMode == newDragMode) {
                        if (dragItem != newDragItem) {
                            dragItem = newDragItem;
                            setDirty();
                            if (dragMode == FRAME_DRAG_BUTTONS) whyDirty |= FRAME_DIRTY_TITLE;
                            else if (dragMode == FRAME_DRAG_SCROLLBAR_HORIZ) whyDirty |= FRAME_DIRTY_SCROLLHORIZ;
                            else whyDirty |= FRAME_DIRTY_SCROLLVERT;
                        }
                    }
                    else {
                        if (dragItem != FRAME_RESIZE_NONE) {
                            dragItem = FRAME_RESIZE_NONE;
                            setDirty();
                            if (dragMode == FRAME_DRAG_BUTTONS) whyDirty |= FRAME_DIRTY_TITLE;
                            else if (dragMode == FRAME_DRAG_SCROLLBAR_HORIZ) whyDirty |= FRAME_DIRTY_SCROLLHORIZ;
                            else whyDirty |= FRAME_DIRTY_SCROLLVERT;
                        }
                    }
                }
                // Resize?
                else if (dragMode == FRAME_DRAG_BORDER) {
                    if (parent == desktop) {
                        int newHeight = height;
                        int newWidth = width;
                        int newX = x;
                        int newY = y;

                        // 2 = titlebar inner border (1) times two
                        int minWidth = outerBorder * 2 + 2 + titlebarButtonTotalWidth;
                        int minHeight = outerBorder * 2 + FRAME_INNERBORDER * 2 + titlebarHeight;

                        // Only apply changes as appropriate
                        // Bottom
                        if (dragItem & FRAME_RESIZE_BOTTOM) {
                            newHeight += event->motion.yrel;
                        }
                        // Top
                        if (dragItem & FRAME_RESIZE_TOP) {
                            newY = y + event->motion.yrel;
                            newHeight -= event->motion.yrel;
                        }
                        // Overflow
                        if (dragItemOverflowY) {
                            newHeight += dragItemOverflowY;
                            if (dragItem & FRAME_RESIZE_TOP) newY -= dragItemOverflowY;
                            dragItemOverflowY = 0;
                        }
                        if (newHeight < minHeight) {
                            dragItemOverflowY = newHeight - minHeight;
                            if (dragItem & FRAME_RESIZE_TOP) newY += dragItemOverflowY;
                            newHeight = minHeight;
                        }
                        if (newY + newHeight > clipH) {
                            dragItemOverflowY = newY + newHeight - clipH;
                            newHeight -= dragItemOverflowY;
                        }
                        if (newY < clipY) {
                            dragItemOverflowY = clipY - newY;
                            newHeight -= dragItemOverflowY;
                            newY = clipY;
                        }
                        // Right
                        if (dragItem & FRAME_RESIZE_RIGHT) {
                            newWidth += event->motion.xrel;
                        }
                        // Left
                        if (dragItem & FRAME_RESIZE_LEFT) {
                            newX = x + event->motion.xrel;
                            newWidth -= event->motion.xrel;
                        }
                        // Overflow
                        if (dragItemOverflowX) {
                            newWidth += dragItemOverflowX;
                            if (dragItem & FRAME_RESIZE_LEFT) newX -= dragItemOverflowX;
                            dragItemOverflowX = 0;
                        }
                        if (newWidth < minWidth) {
                            dragItemOverflowX = newWidth - minWidth;
                            if (dragItem & FRAME_RESIZE_LEFT) newX += dragItemOverflowX;
                            newWidth = minWidth;
                        }
                        if (newX + newWidth > clipW) {
                            dragItemOverflowX = newX + newWidth - clipW;
                            newWidth -= dragItemOverflowX;
                        }
                        if (newX < clipX) {
                            dragItemOverflowX = clipX - newX;
                            newWidth -= dragItemOverflowX;
                            newX = clipX;
                        }

                        move(newX, newY);
                        resize(newWidth, newHeight);
                        
                        // If we didn't reach target size, "undo" some
                        if ((width != newWidth) || (height != newHeight)) {
                            if (width != newWidth) {
                                int offBy = newWidth - width;
                                if (dragItem & FRAME_RESIZE_LEFT) newX += offBy;
                                newWidth -= offBy;
                                dragItemOverflowX += offBy;
                            }
                            if (height != newHeight) {
                                int offBy = newHeight - height;
                                if (dragItem & FRAME_RESIZE_TOP) newY += offBy;
                                newHeight -= offBy;
                                dragItemOverflowY += offBy;
                            }

                            move(newX, newY);
                            resize(newWidth, newHeight);
                        }
                        
                        setDirty();
                        whyDirty = FRAME_DIRTY_ALL;
                    }
                    else {
                        // @TODO: resize within other window
                    }
                }
            }
            // Just hover?
            else {
                doMouseHover(event->motion.x, event->motion.y, hasFocus, clientEvent);
            }
            break;
            
        case SDL_INPUTFOCUS:
            if ((event->user.code & 1) && (!haveFocus)) {
                haveFocus = 1;
                setDirty();
                whyDirty |= FRAME_DIRTY_TITLE;
                
                // Propogate to client
                for (pos = 0; pos < (int)clientArea.size(); ++pos) {
                    if (clientArea[pos].window) {
                        clientEvent.user.code = pos == subFocus ? 1 : 2;
                        clientArea[pos].window->event(hasFocus, &clientEvent);
                    }
                }
            }
            else if (!(event->user.code & 1) && (haveFocus)) {
                haveFocus = 0;
                dragMode = FRAME_DRAG_NONE;
                setDirty();
                whyDirty |= FRAME_DIRTY_TITLE;

                // Propogate to client
                for (pos = 0; pos < (int)clientArea.size(); ++pos) {
                    if (clientArea[pos].window) {
                        clientArea[pos].window->event(hasFocus, &clientEvent);
                    }
                }
            }
            break;
            
        case SDL_KEYDOWN:
        case SDL_KEYUP:
            // Propogate to client- current focus area only
            if (clientArea[subFocus].window) {
                if (clientArea[subFocus].window->event(hasFocus, &clientEvent)) {
                    return 1;
                }
            }
            // If current focus is tool panel or bar, give main area a shot at it
            if ((subFocus != 0) && (clientArea[0].window)) {
                // Don't do this for modifier keys
                if ((clientEvent.key.keysym.sym != SDLK_LSHIFT) &&
                    (clientEvent.key.keysym.sym != SDLK_RSHIFT) &&
                    (clientEvent.key.keysym.sym != SDLK_LALT) &&
                    (clientEvent.key.keysym.sym != SDLK_RALT) &&
                    (clientEvent.key.keysym.sym != SDLK_LCTRL) &&
                    (clientEvent.key.keysym.sym != SDLK_RCTRL)) {
                    if (clientArea[0].window->event(hasFocus, &clientEvent)) {
                        // Switch focus to it
                        focusToolPanel(0);
                        return 1;
                    }
                }
            }
            return 0;
            
        case SDL_COMMAND:
            // Catch certain commands
            switch (event->user.code) {
                case WINDOW_NEXT:
                    nextWindow();
                    return 1;
                    
                case WINDOW_PREV:
                    prevWindow();
                    return 1;
                    
                case WINDOW_CLOSE:
                    closeWindow(); // Calls attemptClose
                    return 1;
                    
                case WINDOW_MIN:
                    if (state == FRAME_STATE_MINIMIZED) restore();
                    else minimize();
                    return 1;

                case WINDOW_MAX:
                    if (state == FRAME_STATE_MAXIMIZED) restore();
                    else maximize();
                    return 1;

                case WINDOW_CASCADE:
                    // (Reset cascade position)
                    cascadeStep = -1;
                case WINDOW_CLOSEALL:
                case WINDOW_CLOSEALLBUT:
                case WINDOW_TILEHORIZ:
                case WINDOW_TILEVERT: {
                    Window* nextWin = NULL;
                    int count = 0;
                    int cascadeOffset = outerBorder + titlebarHeight;
                    int cascadeWidth = desktop->desktopWidth() - cascadeOffset * 3;
                    int cascadeHeight = desktop->desktopHeight() - cascadeOffset * 3;

                    // First pass- does closes/cascades
                    // (assignment intentional)
                    while ( (nextWin = desktop->findWindow(WINDOW_FRAME, count)) ) {
                        ++count;
                        FrameWindow* fwin = dynamic_cast<FrameWindow*>(nextWin);
                        if (fwin) {
                            if ((event->user.code == WINDOW_CLOSEALL) ||
                                ((event->user.code == WINDOW_CLOSEALLBUT) && (fwin != this))) {
                                fwin->closeWindow();
                            }
                            else if (event->user.code == WINDOW_CASCADE) {
                                fwin->restore();
                                if (++cascadeStep > 3) cascadeStep = 0;
                                fwin->move(desktop->desktopX() + cascadeStep * cascadeOffset,
                                           desktop->desktopY() + cascadeStep * cascadeOffset);
                                fwin->resize(cascadeWidth, cascadeHeight);
                            }
                        }
                    }
                    
                    // Do another pass for tiling
                    if ((event->user.code == WINDOW_TILEHORIZ) || (event->user.code == WINDOW_TILEVERT)) {
                        // Calculate minimum rows/columns to hold 'count' windows
                        // Special exception- 3 or less
                        int rows = 1, cols = 1;
                        if (count <= 3) rows = count;
                        else {
                            while (rows * cols < count) {
                                if (rows == cols) ++rows;
                                else ++cols;
                            }
                        }
                        
                        // Number of entries in each row (horiz) or col (vert)
                        int* entries = new int[rows];
                        memset(entries, 0, sizeof(int[rows]));
                        
                        // Fill up the rows, from right-to-left (bottom-to-top)
                        int row = rows;
                        for (int pos = 0; pos < count; ++pos) {
                            ++entries[--row];
                            if (row == 0) row = rows;
                        }
                        
                        // Now we move windows, fill last spot first
                        int currRow = rows - 1;
                        int currCol = entries[currRow] - 1;
                        
                        int pos = 0;
                        // (assignment intentional)
                        while ( (nextWin = desktop->findWindow(WINDOW_FRAME, pos++)) ) {
                            FrameWindow* fwin = dynamic_cast<FrameWindow*>(nextWin);
                            if (fwin) {
                                fwin->restore();
                                int width, height;
                                if (event->user.code == WINDOW_TILEVERT) {
                                    // row/width are swapped
                                    width = desktop->desktopWidth() / rows;
                                    height = desktop->desktopHeight() / entries[currRow];
                                    fwin->move(desktop->desktopX() + width * currRow,
                                               desktop->desktopY() + height * currCol);
                                }
                                else {
                                    width = desktop->desktopWidth() / entries[currRow];
                                    height = desktop->desktopHeight() / rows;
                                    fwin->move(desktop->desktopX() + width * currCol,
                                               desktop->desktopY() + height * currRow);
                                }
                                fwin->resize(width, height);
                            }
                            // Next spot
                            if (--currCol < 0) {
                                --currRow;
                                currCol = entries[currRow] - 1;
                            }
                        }
                        
                        delete[] entries;
                    }

                    return 1;
                }
                    
                case WINDOW_SWITCHPANEL:
                    // @TODO:
                        /*
                    if ((subFocus == 0) && (clientArea[1])) {
                        // If not open, open it- this switches focus too
                        if (!toolPanelOpen) toggleToolPanel();
                        else focusToolPanel(1);
                        return 1;
                    }
                    if ((subFocus < 2) && (clientArea[2])) {
                        if (!toolBarOpen) toggleToolBar();
                        else focusToolPanel(2);
                        return 1;
                    }
                    focusToolPanel(0);
                    */
                    return 1;
                    
                case VIEW_TOOLPANEL:
                    // @TODO:
                        /*
                    toggleToolPanel();
                    */
                    return 1;

                case VIEW_TOOLBAR:
                    // @TODO:
                        /*
                    toggleToolBar();
                    */
                    return 1;
            }

            // Propogate to client- try current panel, then others
            if ((clientArea[subFocus].window) && (exempt != clientArea[subFocus].window)) {
                if (clientArea[subFocus].window->event(hasFocus, &clientEvent)) return 1;
            }
            for (pos = 0; pos < (int)clientArea.size(); ++pos) {
                if (pos != subFocus) {
                    if ((clientArea[pos].window) && (exempt != clientArea[pos].window)) {
                        if (clientArea[pos].window->event(0, &clientEvent)) return 1;
                    }
                }
            }
            return 0;

        case SDL_SPECIAL:
            // Scrollbar repeat?
            if ((event->user.code & SDL_IDLE) && (scrollRepeatDelay)) {
                ticks = SDL_GetTicks();
                // (catch wraparound)
                if (ticks < scrollRepeatTicks) scrollRepeatTicks = SDL_GetTicks();
                else if (ticks - scrollRepeatTicks > scrollRepeatDelay) {
                    scrollRepeatTicks = SDL_GetTicks();
                    scrollRepeatDelay = DELAY_SCROLLBAR_REPEATAGAIN;
                    if (dragMode == FRAME_DRAG_SCROLLBAR_VERT) {
                        if (dragItem == FRAME_SCROLL_LESS) scrollTo(scrollX, scrollY + vScrollLine);
                        else if (dragItem == FRAME_SCROLL_MORE) scrollTo(scrollX, scrollY - vScrollLine);
                        else if (dragItem == FRAME_SCROLL_PAGELESS) scrollTo(scrollX, scrollY + vScrollPage);
                        else if (dragItem == FRAME_SCROLL_PAGEMORE) scrollTo(scrollX, scrollY - vScrollPage);
                    }
                    if (dragMode == FRAME_DRAG_SCROLLBAR_HORIZ) {
                        if (dragItem == FRAME_SCROLL_LESS) scrollTo(scrollX + hScrollLine, scrollY);
                        else if (dragItem == FRAME_SCROLL_MORE) scrollTo(scrollX - hScrollLine, scrollY);
                        else if (dragItem == FRAME_SCROLL_PAGELESS) scrollTo(scrollX + hScrollPage, scrollY);
                        else if (dragItem == FRAME_SCROLL_PAGEMORE) scrollTo(scrollX - hScrollPage, scrollY);
                    }
                    // Force mouse motion to determine new drag mode
                    SDL_GetMouseState(&mX, &mY);
                    SDL_WarpMouse(mX, mY);
                    used = 1;
                }
            }
            // (continue on, to propogate to client)
            
        case SDL_QUIT:
        case SDL_SYSKEY:
        case SDL_OBJECTCHANGE:
            // (exempt window may exist)
            exempt = (Window*)clientEvent.user.data2;
        default:
            // Propogate to client
            for (pos = 0; pos < (int)clientArea.size(); ++pos) {
                if ((clientArea[pos].window) && (exempt != clientArea[pos].window)) {
                    used = clientArea[pos].window->event(hasFocus && (subFocus == pos), &clientEvent) || used;
                }
            }
            return used;
    }

    // All mouse and focus events, we use.
    return 1;
}

void FrameWindow::doMouseHover(int x, int y, int hasFocus, SDL_Event& clientEvent) { start_func
    DragType newDragMode;
    DragSubType newDragItem;

    whereCoords(x, y, &newDragMode, &newDragItem);
    if (newDragMode == FRAME_DRAG_BORDER) {
        if ((newDragItem == FRAME_RESIZE_BOTTOM) || (newDragItem == FRAME_RESIZE_TOP))
            selectMouse(MOUSE_VERT);
        else if ((newDragItem == FRAME_RESIZE_LEFT) || (newDragItem == FRAME_RESIZE_RIGHT))
            selectMouse(MOUSE_HORIZ);
        else if ((newDragItem == (FRAME_RESIZE_LEFT | FRAME_RESIZE_BOTTOM)) || (newDragItem == (FRAME_RESIZE_RIGHT | FRAME_RESIZE_TOP)))
            selectMouse(MOUSE_DIAGUP);
        else if ((newDragItem == (FRAME_RESIZE_LEFT | FRAME_RESIZE_TOP)) || (newDragItem == (FRAME_RESIZE_RIGHT | FRAME_RESIZE_BOTTOM)))
            selectMouse(MOUSE_DIAGDOWN);
    }
    else if (newDragMode == FRAME_DRAG_TITLE) selectMouse(MOUSE_FOURDIRECTION);
    else if ((newDragMode != hoverMode) || (newDragItem != hoverItem)) selectMouse(MOUSE_NORMAL);
    if ((hoverMode == FRAME_DRAG_CLIENT) && (clientArea[hoverItem].window) && ((newDragMode != hoverMode) || (newDragItem != hoverItem))) {
        // Lose focus
        SDL_Event customEvent;
        customEvent.type = SDL_MOUSEFOCUS;
        customEvent.user.code = 0;
        customEvent.user.data1 = NULL;
        customEvent.user.data2 = NULL;
        clientArea[hoverItem].window->event(hasFocus && (subFocus == hoverItem), &customEvent);
    }
    if (newDragMode == FRAME_DRAG_CLIENT) {
        if (clientArea[newDragItem].window) {
            // Gain focus?
            if ((newDragMode != hoverMode) || (newDragItem != hoverItem)) {
                SDL_Event customEvent;
                customEvent.type = SDL_MOUSEFOCUS;
                customEvent.user.code = 1;
                customEvent.user.data1 = NULL;
                customEvent.user.data2 = NULL;
                clientArea[newDragItem].window->event(hasFocus && (subFocus == newDragItem), &customEvent);

                // Force to be a mouse motion event if gaining focus
                if (clientEvent.type == SDL_MOUSEBUTTONUP) {
                    int x = clientEvent.button.x;
                    int y = clientEvent.button.y;
                    clientEvent.type = SDL_MOUSEMOTION;
                    clientEvent.motion.x = x;
                    clientEvent.motion.y = y;
                    clientEvent.motion.state = SDL_GetMouseState(NULL, NULL);
                    clientEvent.motion.xrel = 0;
                    clientEvent.motion.yrel = 0;
                }
            }
    
            // Only propogate hovering- MouseUp was alredy done
            if (clientEvent.type != SDL_MOUSEBUTTONUP) {
                // Adjust coordinates to 0-based for client
                if (clientEvent.type == SDL_MOUSEMOTION) {
                    clientEvent.motion.x -= clientArea[newDragItem].x + (newDragItem == 0 ? scrollX : 0);
                    clientEvent.motion.y -= clientArea[newDragItem].y + (newDragItem == 0 ? scrollY : 0);
                }
                else {
                    clientEvent.button.x -= clientArea[newDragItem].x + (newDragItem == 0 ? scrollX : 0);
                    clientEvent.button.y -= clientArea[newDragItem].y + (newDragItem == 0 ? scrollY : 0);
                }
                
                // Propogate to client
                clientArea[newDragItem].window->event(hasFocus && (subFocus == newDragItem), &clientEvent);
            }
        }
    }
    hoverMode = newDragMode;
    hoverItem = newDragItem;
}
